Skip to content

Instantly share code, notes, and snippets.

@pascalpp
Last active May 25, 2017 19:45
Show Gist options
  • Save pascalpp/645ba8beb952290f0ba15689e1e6cb76 to your computer and use it in GitHub Desktop.
Save pascalpp/645ba8beb952290f0ba15689e1e6cb76 to your computer and use it in GitHub Desktop.
RenderOne React Component idea

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. <div>a div</div> I've made it so that any static elements, such as <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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment