Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language.
Problem
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class App extends React.Component { render() { return <Toolbar theme="dark" />; } } function Toolbar(props) { // The Toolbar component must take an extra "theme" prop // and pass it to the ThemedButton. This can become painful // if every single button in the app needs to know the theme // because it would have to be passed through all components. return ( <div> <ThemedButton theme={props.theme} /> </div> ); } class ThemedButton extends React.Component { render() { return <Button theme={this.props.theme} />; } } |
Using context, we can avoid passing props through intermediate elements:
Solution
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// Context lets us pass a value deep into the component tree // without explicitly threading it through every component. // Create a context for the current theme (with "light" as the default). const ThemeContext = React.createContext('light'); class App extends React.Component { render() { // Use a Provider to pass the current theme to the tree below. // Any component can read it, no matter how deep it is. // In this example, we're passing "dark" as the current value. return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } // A component in the middle doesn't have to // pass the theme down explicitly anymore. function Toolbar() { return ( <div> <ThemedButton /> </div> ); } class ThemedButton extends React.Component { // Assign a contextType to read the current theme context. // React will find the closest theme Provider above and use its value. // In this example, the current theme is "dark". static contextType = ThemeContext; render() { return <Button theme={this.context} />; } } |
What is children prop?
Children is a prop this.prop.children that allow you to pass components as data to other components, just like any other prop you use. Component tree put between component’s opening and closing tag will be passed to that component as children prop.
There are a number of methods available in the React API to work with this prop. These include React.Children.map, React.Children.forEach, React.Children.count, React.Children.only, React.Children.toArray.
A simple usage of children prop looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const MyDiv = React.createClass({ render: function() { return <div>{this.props.children}</div> } }) ReactDOM.render( <MyDiv> <span>{'Hello'}</span> <span>{'World'}</span> </MyDiv>, node ) |
If you only want to avoid passing some props through many levels, component composition is often a simpler solution than context
Component composition
Components use the special children prop to pass children elements directly into their output:
1 2 3 4 5 6 7 |
function FancyBorder(props) { return ( <div className={'FancyBorder FancyBorder-' + props.color}> {props.children} </div> ); } |
This lets other components pass arbitrary children to them by nesting the JSX:
1 2 3 4 5 6 7 8 9 10 11 12 |
function WelcomeDialog() { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> Welcome </h1> <p className="Dialog-message"> Thank you for visiting our spacecraft! </p> </FancyBorder> ); } |
You can also specify where you want children props to go. In our case, we can position prop children to be placed in certain areas.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
function SplitPane(props) { return ( <div className="SplitPane"> <div className="SplitPane-left"> {props.left} </div> <div className="SplitPane-right"> {props.right} </div> </div> ); } function App() { return ( <SplitPane left={ <Contacts /> } right={ <Chat /> } /> ); } |
We can even specialize it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function Dialog(props) { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> {props.title} </h1> <p className="Dialog-message"> {props.message} </p> </FancyBorder> ); } function WelcomeDialog() { return ( <Dialog title="Welcome" message="Thank you for visiting our spacecraft!" /> ); } |
Inversion of Control
It might feel redundant to pass down the user and avatarSize props through many levels if in the end only the Avatar component really needs it. It’s also annoying that whenever the Avatar component needs more props from the top, you have to add them at all the intermediate levels too.
One way to solve this issue without context is to pass down the Avatar component itself so that the intermediate components don’t need to know about the user or avatarSize props:
1 2 3 4 5 6 7 8 9 |
function Page(props) { const user = props.user; const userLink = ( <Link href={user.permalink}> <Avatar user={user} size={props.avatarSize} /> </Link> ); return <PageLayout userLink={userLink} />; } |
This inversion of control can make your code cleaner in many cases by reducing the amount of props you need to pass through your application and giving more control to the root components. However, this isn’t the right choice in every case: moving more complexity higher in the tree makes those higher-level components more complicated and forces the lower-level components to be more flexible than you may want.
You’re not limited to a single child for a component. You may pass multiple children, or even have multiple separate “slots” for children, as documented here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
function Page(props) { const user = props.user; const content = <Feed user={user} />; const topBar = ( <NavigationBar> <Link href={user.permalink}> <Avatar user={user} size={props.avatarSize} /> </Link> </NavigationBar> ); return ( <PageLayout topBar={topBar} content={content} /> ); } |