I'm trying to do this:
<RenderOne {...props}>
<ComponentOne />
<ComponentTwo />
<ComponentThree />
</RenderOne>
such that only the first non-null element is rendered. That is, if ComponentOne returns null, and ComponentTwo returns an element, and ComponentThree returns an element, then only ComponentTwo will be rendered. The output of ComponentThree will be ignored since ComponentTwo precedes it and is non-null.
An example use-case: suppose a user logs into your site, and there are various prompts that you might want to show them, but you only want to show them the first one that applies. I find it simpler to stack the components like the above example, and have them return null if they don't apply to the current viewer.
This idea is similar to the Switch component in react-router. That component can tell in advance if a child route will render, using only its path prop and the current location. But in my case, I would like the encapsulate the logic within each component, which I think means I need to render each child in order to assess its output.
Here's what I have implemented:
function RenderOne(props) {
let Component
React.Children
.toArray(props.children)
.forEach((child) => {
// if child isn't an element, skip it
if (! React.isValidElement(child)) return
// if Component was defined in a previous pass, return
if (Component) return
if (typeof child.type === 'function') {
// render the child
const render_props = _.extend({}, child.props, props)
let result
if (child.type.prototype.render) {
const ElementType = child.type
const instance = new ElementType(render_props)
result = instance.render()
} else {
result = child.type(render_props)
}
// if the render returns a result, set Component to this child
if (result) Component = child
} else {
Component = child
}
})
if (Component && typeof Component.type === 'string') return Component
return Component ? React.cloneElement(Component, props) : null
}
This only works if each child is a class or a functional component. It ignores non-elements and throws if you pass in a static element, e.g. I've made it so that any static elements, such as <div>a div</div>
<div>a div</div>
will render as is. This allows you to put a static element as a fallback at the bottom of a stack of conditional components.
I'm kind of new to React, so this feels a bit odd to me, like I'm 'doing it wrong' to render each child like this in order to evaluate its output. Is this a bad idea? Or if the idea is okay, is there a way you'd improve on this implementation?