Steven Natera


3 easy steps to writing compound components

Let’s dive into the world of clean React code with compound components.

After implementing my first compound component in React, I’m officially addicted to this pattern. As the self-proclaimed expert on the matter (actually I learned from Ryan Florence and his talk on compound components), I’m going to teach you how to learn the best advanced React pattern out there in a few simple steps. Why? Because you deserve a better codebase!

At this point you might be thinking, who is this guy? He’s not Kent C. Dodds, who wrote the course on compound components. He’s not our beloved React prophet Dan Abramov. Well, my response to you is you’re correct!

You’re correct dancing horse for everyone!

That’s right, I am nobody you once knew. But in case you do want to know me my name is Steven. You can follow me on Twitter. But while I am a nobody, if you’re excited by the thought of compound components, this is the guide for you.

Compound components leave your JSX markup clean. You can truly focus on presentation while the logic is tucked away in container components. The first time I wrote a compound component, a 1 hour coding session turned into an all night programming jam. I could not resist refactoring the whole codebase of my side project.

In those 6 hours I developed a mental guide to take the guesswork out of designing your components. And best of all, I’m here to share it with you to keep your codebase happy.

The 3 Step Guide to Compound Components

Ok time for your nonsense free guide to writing compound components. Today you’re going to build a multi-step form wizard. Once you’re done, you’ll have a clean, reusable form wizard you can use in any React project.

When you work with compound components you must remember this pattern is all about passing props programmatically as Ryan Florence once said. If you can wrap your head around that concept then its smooth sailing from here.

Step 1. Mock out your desired JSX markup

You want to make the JSX markup easy to read by grouping related components. The approach is extensible as adding another step or button is as simple as adding another component to the relevant component group. The logic that powers the fundamental components at the bottom will come from the component above. StepList and ButtonList make up the first level of the related components.

A Step renders a given component if its the active step. Previous goes back a step if possible. Next moves forward a step if possible. Submit displays the submit message but only if its the last step. StepList and ButtonList pass down props to that determine which buttons to render on a given step and what step is active.

Step 2. Determine what props your components will need

From here you think about what props each container component needs. StepList needs to determine the current step to present to the user. Use a current step prop. ButtonList shows the Previous, Next, and Submit buttons on certain steps. An active step and total steps help determine when to show each button. You need a submit function because this is a form. Now that you have the props, you’re done! Well, not exactly.

Don’t do this! We want to pass props indirectly.

With you props in mind, you might be tempted to write your props directly on your components like you see above but there is a cleaner way to pass props. The better way is to pass your props indirectly. How you ask? The React API has a couple useful methods you can use to create new components from existing ones. That’s where React.Children and React.cloneElement will become useful.

Step 3. Wire your components with the React API

The best part of React is that a component is a regular objects. As with any object, you can manipulate them to pass around different props and functionality without having to annotate JSX components directly. React.Children will allow you to iterate over the children of a component. React.cloneElement will allow you to create new component with additional props from an existing component element.

Here you iterate the children in FormWizard using React.Children. As you iterate, use React.cloneElement to create a new component with the desired props you want to pass down the component tree.

The advancement functions, onNextStep and onPreviousStep, are used by Next and Previous buttons. The handleSubmit is used by Submit. We add these props to the new ButtonList component to pass downward. The activeStepIndex prop helps both the Step components and form buttons determine if they should render.

The new Step component will get an isActive prop from StepList to know when to render a step. The functions and state props in the ButtonList component go to the corresponding buttons that need them. Our form hides the previous button on the first step, hides the next button on the last step, and shows the submit button on the last step. Outside these cases the buttons are visible.

Finally, use the props passed to the base components to determine what should be rendered. See, simple! Sorta. I admit there is a lot to wrap your head around. But here’s the best part, Oprah and I are both Aquarius (what’s the plural of Aquarius? Aquarii?) therefore: you get a demo, you get a demo, everybody gets a demo! Here is a demo of the guide.

With React 16.3 you could use the Context API to make the FormWizard even more flexible in regards to props. Since your application is only a few levels deep you should follow the advice given in the docs. Use Context when you are passing down the same data to many components, at multiple levels.

If you enjoyed this dope techno guide then join the Dope Technophiles newsletter where we share the dope shit we’re building. Now go forth and spread the word of compound components. Share this post with a friend to make them dope!

May 12th, 2018

👋🏽Hi! I’m Steven Natera. Follow me on Twitter @StevenNatera. I’m actively involved in the open source community, specifically GatsbyJS. I like writing about code, startups, React and Kubernetes.

More by Steven Natera

Topics of interest

More Related Stories