Introducing react-lift-props

Create systems of components using deeply nested children.

The problem

There’s a cool pattern in React that people use when they want to create some components that work together to make up a larger system. I haven’t really found a name for it, but it looks something like this:

<Stepper>

<Step name="Select campaign settings">

For each ad campaign that you create, you can...

</Step>

<Step name="Create an ad group">

...

</Step>

<Step name="Create an ad">

...

</Step>

</Stepper>

material-ui’s fantastic stepper component

It looks pretty simple on the surface, but if you think about it you can see the Stepper is doing some magic here. The Stepper is looping over it’s children, and looking at the “name” prop. It then takes that name prop and creates a header with a circle with the step number in it. It looks something like this:

const Stepper = ({ children }) => (

<div>

{children.map((child, idx) => (

<div>

<StepTitle

number={idx}

title={child.props.name}

/>

<Collapsible>

{child.props.children}

</Collapsible>

</div>

)}

</div>

);

This is pretty cool, but there’s a limitation to it. What if I wanted to wrap a Step inside a different component so I could encapsulate the step name and/or other properties of the Step . For example, let’s say I wanted to do this:

const SelectSettingsStep = () => {

return (

<Step name="Select campaign settings">

For each ad campaign that you create, you can...

</Step>

)

}

There’s many reasons we might want to do this:

Code re-use : Now we can re-use the SelectSettingsStep in multiple different Steppers .

: Now we can re-use the in multiple different . Adaptable : It’s much easier to change the SelectSettingsStep now without worrying about affecting the rest of the app. For example, maybe we decide the SelectSettingsStep should actually be two steps. Now we can easily just add another Step as a child of SelectSettingsStep .

: It’s much easier to change the now without worrying about affecting the rest of the app. For example, maybe we decide the should actually be two steps. Now we can easily just add another as a child of . Encapsulation : What if we wanted to slightly change the step name based on some criteria? That logic doesn’t belong in the component we used the Stepper in, it should go in this SelectSettingsStep , which we can now do.

: What if we wanted to slightly change the step name based on some criteria? That logic doesn’t belong in the component we used the in, it should go in this , which we can now do. Information hiding: This approach also let’s us hide away the information and complexity. This helps make it more adaptable, but also makes the place we use the Stepper easier to read and understand.

<Stepper>

< SelectSettingsStep />

<CreateAdGroupStep />

<CreateAdStep />

</Stepper>

Testable : It’s much easier to write unit tests for the SelectSettingsStep since we’ve separated it out into a smaller, more manageable chunk.

: It’s much easier to write unit tests for the since we’ve separated it out into a smaller, more manageable chunk. Cleaner: We’ve also just generally reduced clutter and made it easier on the eye to read.

Ok, so now we’re really excited and we want to split this up. But we can’t. The Stepper isn’t written in a way that allows this. That’s because of this here:

<StepTitle

number={idx}

title={child.props.name}

/>

You can see we do child.props.name , but there is no name on the child anymore. This really sucks. Your next thought might be to try child.props.children[0].props.name , but that doesn’t work because there’s no children either.

<Stepper>

< SelectSettingsStep />

^^^ no name or children here

The solution

That’s where react-lift-props comes in. It’s a small library that makes use of the React context API to solve this problem. It lets you create components called Lifters that will lift the props to the nearest component wrapped with withLiftedProps . All we need to do is change our Step to this:

import { createLifter } from 'react-lift-props'; export default Step = createLifter({ displayName: 'Step' });

And then change our Stepper code to use withLiftedProps and loop over this.props.liftedProps instead of this.props.children like so:

const Stepper = withLiftedProps(({ liftedProps }) => (

<div>

{ liftedProps .map((stepProps, idx) => (

<div>

<StepTitle

number={idx}

title={stepProps.name}

/>

<Collapsible>

{stepProps.children}

</Collapsible>

</div>

)}

</div>

));

🎉 And that’s it. Now you can create systems of components using deeply nested children. Check it out on GitHub and npm .

Notes