I\'m trying to return multiple React elements from a helper method. I could solve it simply by moving around some code, but I\'m wondering if there\'s a cleaner way to solve
I like to have an If-component around for such things, and I have wrapped everything into a span, as it doesn't really break anything and makes the need for keys go away...
const getAuthorUrl = author => `/${author.toLowerCase()}`;
function If({condition,children}) {
return condition ? React.Children.only(children) : null;
}
class Foo extends React.Component {
render() {
return (
<div>
{this.props.datePosted}
<If condition={this.props.author}>
<span> by
<a key="author" href={getAuthorUrl(this.props.author)}>
{this.props.author}
</a>
</span>
</If>
</div>
);
}
}
ReactDOM.render(<Foo datePosted="Today" author="Me"/>, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
...skipping the array thing altogether?
There is another way to solve this. I will suggest you create another component Author.js:
function Author(props) {
return (<span>
<span> by </span>
<a href={props.getAuthorUrl(props.author)}>{props.author}</a>
</span>)
}
class Foo extends React.Component {
render() {
return (
<div>
{this.props.title}
{this.props.author && <Author author={this.props.author} getAuthorUrl={this.getAuthorUrl} />}
</div>
);
}
}
I didn't test this code though. But it will look more cleaner I think. Hope it helps.
It's not currently possible to do this without some sort of workaround like wrapping everything in another component, since it ends up with the underlying React code trying to return multiple objects.
See this active Github issue where support for this is being considered for a future version though.
Edit: You can now do this with Fragments in React 16, see: https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html
This is a bit hacky but it doesn't have unnecessary jsx as you wished.
var author = 'Daniel';
var title = 'Hello';
var Hello = React.createClass({
_renderAutho0r: function() {
if (!author) {
return null;
}
return <a href="#">{author}</a>
},
render: function() {
var by = author ? ' by ' : null;
return (
<div>
{title}
{by}
{this._renderAutho0r()}
</div>
);
}
});
React.render(<Hello name="World" />, document.body);
my JSFiddle
Try this:
class Foo extends React.Component {
_renderAuthor() {
return <a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>
}
render() {
return (
<div>
{this.props.title}
{this.props.author && " by "}
{this.props.author && this._renderAuthor()}
</div>
);
}
}
You can return fragments from sub-rendering functions but not from the main render function, at least before React 16. In order to do so, return an array of components. You don't need to set keys manually unless your fragment children will change (arrays are keyed with indices by default).
For creating fragments you may also use createFragment.
For inline usage, you may use an array or leverage immediately invoked arrow function. See the example below:
const getAuthorUrl = author => `/${author.toLowerCase()}`;
class Foo extends React.Component {
constructor() {
super();
this._renderAuthor = this._renderAuthor.bind(this);
this._renderUser = this._renderUser.bind(this);
}
_renderAuthor() {
if (!this.props.author) {
return null;
}
return [
' by ',
<a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
];
}
_renderUser() {
return [
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
]
}
render() {
return (
<div>
{this.props.datePosted}
{this._renderAuthor()}
<div>
{this.props.user
? this._renderUser()
: <p>User not found</p>}
</div>
<div>
{this.props.user
? [
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
]
: <p>User not found</p>}
</div>
<div>
{this.props.user
? (() => [
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
])()
: <p>User not found</p>}
</div>
</div>
);
}
}
ReactDOM.render(<Foo datePosted="Today" author="Me" user={{name: 'test', info: 'info'}} />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
In order to not get warnings each child must be assigned a key. In order to do so, instead of returning an array please use helper function fragment(...children)
to assign index-based keys automatically. Please note that strings must be converted to spans or other nodes that can be assigned with a key:
const fragment = (...children) =>
children.map((child, index) =>
React.cloneElement(
typeof child === 'string'
? <span>{child}</span>
: child
, { key: index }
)
)
const getAuthorUrl = author => `/${author.toLowerCase()}`;
const fragment = (...children) =>
children.map((child, index) =>
React.cloneElement(
typeof child === 'string'
? <span>{child}</span>
: child
, { key: index }
)
)
class Foo extends React.Component {
constructor() {
super();
this._renderAuthor = this._renderAuthor.bind(this);
this._renderUser = this._renderUser.bind(this);
}
_renderAuthor() {
if (!this.props.author) {
return null;
}
return fragment(
' by ',
<a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>
);
}
_renderUser() {
return fragment(
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
)
}
render() {
return (
<div>
{this.props.datePosted}
{this._renderAuthor()}
<div>
{this.props.user
? this._renderUser()
: <p>User not found</p>}
</div>
<div>
{this.props.user
? fragment(
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
)
: <p>User not found</p>}
</div>
<div>
{this.props.user
? (() => fragment(
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
))()
: <p>User not found</p>}
</div>
</div>
);
}
}
ReactDOM.render(<Foo datePosted="Today" author="Me" user={{name: 'test', info: 'info'}} />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>