I've heard of react-templates, but I was still wondering it was possible to make a first-class ForEach component.
My end goal is to make something like this more readable:
<ul>
{list.map(function(item, i) {
return <li>{item}</li>;
})}
</ul>
// instead?
<ul>
<ForEach items="{list}">
<li>{item}</li>
</ForEach>
</ul>
Here's my first serious attempt by passing props:
var ForEach = React.createClass({
render: function(){
return (
<ul>
{this.props.items.map(function(item, i) {
return React.Children.map(this.props.children, function(child) {
return React.addons.cloneWithProps(child, {item: item})
})
}.bind(this))}
</ul>
);
}
});
var Element = React.createClass({
render: function(){
return (
<li>{this.props.children}</li>
);
}
});
// usage within some other React.createClass render:
<ForEach items={['foo', 'bar', 'baz']}>
<Element>{this.props.item}</Element>
</ForEach>
The challenge I'm running into is what this
points to. By single-stepping with a debugger, I can see that I'm creating cloned elements with this.props.item
set, but because {this.props.item}
is evaluated in the context of some other enclosing component's render
method, this
isn't the cloned Element
component - it's ForEach
's parent.
{this.props.item}
will work inside Element.render
but that's not where I want it - I want to be able to hand Element
some expression that interpolates the current item.
Is this just not possible in React, or is there some way that I could have the ForEach
component pass down state like current item/index to the nested elements?
UPDATE I can get a significant improvement in readability with ES6 arrow functions. One set of curlies goes away, along with the return
(and possibly also a .bind(this)
if you reference this
inside the loop).
<ul>
{list.map((item, i) =>
<li>{item}</li>
)}
</ul>
That goes a long way to help with the syntax clunkiness of doing map
line.
My approach would be to have ForEach
expect a function child that gets called for each item, and simply injects a react element into the render.
It would be used something like this:
render: function() {
return (
<ForEach items={['foo', 'bar', 'baz']}>
{function (item) {
return (
<Element>{item}</Element>
)
}/*.bind(this)*/} // optionally use this too
</ForEach>
)
}
This would look better yet if you made use of ES6 Arrow Functions:
render() {
return (
<ForEach items={['foo', 'bar', 'baz']}>
{(item) => // bind is not needed with arrow functions
<Element>{item}</Element>
}
</ForEach>
)
}
Now, to actually implement ForEach
:
var ForEach = React.createClass({
getDefaultProps: function(){
return {
element: 'ul',
elementProps: {}
};
},
render: function(){
return React.createElement(
// Wrapper element tag
this.props.element,
// Optional props for wrap element
this.props.elementProps,
// Children
this.props.items.map(this.props.children, this)
);
}
});
Pretty simple! The one caveat I've found is that the key
prop needs to be set manually by the itterator function (probably using key={index}
)
Have a look at my basic example
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With