💡 Quick Solution: use React.cloneElement to create a new component with the parent's props.
- Create the Parent component
export function Layout(props) {
return (
<div>
{React.Children.map(props.children, (child) =>
React.cloneElement(child, props)
)}
</div>
);
}
- Use it to render children.
<OtherReactComponent/>
now has access to props of<Layout/>
.
const App = () => (
<Layout isAuthenticated={true}>
// <OtherReactComponent /> has access to isAuthenticated prop
<OtherReactComponent />
<OtherReactComponent />
<OtherReactComponent />
</Layout>
);
Diving Deeper
Everyone knows how to pass props to react components, but your case is different. You don't really know the component you'll pass as a child/children and you want them to inherit the parent's props. A good use case might be a <Protected/>
component. You'll want every child of the component to have access to an isAuthenticated
prop. You've even tried something like this...
export function Layout(props) {
return <div>{props.children(props)}</div>;
}
As you know, this doesn't work.
Why doesn't {props.children(props)} work?
There's a couple of reasons why, but the most important is that react.children
isn't a function and can't be called with arguments (It's possible, but it's just safe to think of it as not).
In fact, it helps to think that React components can only receive the following as valid children...
// A string as child
<Component>String Child</Component>
// Another React component
<Component>
<SomeReactComponent/>
</Component>
// Array of react components
<Component>
<SomeReactComponent/>
<SomeReactComponent/>
<SomeReactComponent/>
</Component>
// A combination of the above
<Component>
<SomeReactComponent/>
<SomeReactComponent/>
Something Else
</Component>
This means we have to pass any of these as children to a component.
Possible Solutions
There are many ways to solve this problem today, but we'll stick to these 2
- React.cloneElement
- Context API
React.cloneElement
React.cloneElement creates a new component from an already existing one. The syntax looks something like this...
React.cloneElement(Component, { ...componentProps });
You'll also want to wrap it in a map
function so it handles the case of multiple React components. See the documentation for more about React.Children.
export function Layout(props) {
return (
<div>
{React.Children.map(props.children, (child) =>
React.cloneElement(child, props)
)}
</div>
);
}
Context API
What if we thought of the problem differently? If your goal is to share props with a component's children, then the Context API is the best solution. You don't have to manually pass props around like some lunatic 🏃🏼♀️🏃🏼♀️🏃🏼♀️. Say yes to clean code!
How to use the Context API
I'm trying to keep this article short, so I'll breeze over concepts and assume you have experience with the context API. If not, you might want to learn about it and why you need it first.
- Wrap the parent component in a
Provider
import { createContext } from "react";
export function Layout(props) {
const LayoutContext = createContext(null);
return (
<LayoutContext.Provider value={props.isAuthenticated}>
<div>{props.children}</div>
</LayoutContext.Provider>
);
}
- Access the values in the child Component with the
useContext
hook
import { useContext } from "react";
export function ChildComponent(props) {
const layoutContextValue = useContext(LayoutContext);
const { isAuthenticated } = layoutContextValue;
if (!isAuthenticated) {
// do something when user isn't loggedIn
}
return (
<div>
<SomeOtherComponent />
</div>
);
}
- Use in your app
export function App() {
return (
<Layout isAuthenticated={true}>
<ChildComponent />
</Layout>
);
}
I hope this post helped you solve your bug. Please reach out to me on Twitter if there's something I'm missing. I'll be glad to edit accordingly.
More Resources
- React.cloneElement
- Context API
- React.Children
- How to pass props to {this.props.children}
- Passing props to children in React
Happy debugging 🎈