How to avoid prop-drilling in React without using Redux (Part 2)

In the first part of my blog post series about the methods of limiting prop-drilling in React, I showed that prop-drilling is generally beneficial and you may usually want it in your applications. I then presented a way for refactoring your components using Inversion of Control principle to limit the number of passed props when their volume becomes overwhelming. In this part, we will see how to use the same strategy in a more complex situation, boosting it with render props. We will then consider an example of an application where a piece of data needs to be passed to almost every leaf component and instead of Redux, we will use React Context to achieve that.

Using render props to limit prop drilling

A variation of the Inversion of Control-based approach can be also used in more complicated situations, when the nested component requiring data from up high (like SearchBar in our last example) needs to receive some additional information from the parent into which it’s "injected". We will now consider such a case. Our next example has a different component structure - have a look below. The arrows point to components that need access to the same piece of data. [cc lang="JavaScript" escaped="true" lines="100"] App Basket <-- AllProductsList ProductList Product <-- Product <-- Product <-- ... [/cc] This example renders a list of products, each with an "Add to basket" button. After we add different products to the basket, their total number is displayed in Basket component. We need to put the state describing current products in the basket in a common ancestor of Product and Basket components, which is App again. Below you can see the full source code. Note that the state of AllProductsList component would normally be populated e.g. by making a request to some backend, while in our case it's hardcoded for simplicity. Here’s the live demo: https://codesandbox.io/s/vyk028p58y [cc lang="JavaScript" escaped="true" lines="100"] class App extends Component { constructor () { super() this.state = { productsInBasket: {} } } addToBasket = (id) => { this.setState(prevState => ({ productsInBasket: { ...prevState.productsInBasket, [id]: prevState.productsInBasket[id] + 1 || 1 } })) } countProductsInBasket = () => ( Object.keys(this.state.productsInBasket).reduce( (total, key) => total + this.state.productsInBasket[key], 0 ) ) render () { return ( <div className="App"> <Basket productCount={this.countProductsInBasket()}/> <AllProductsList handleBuyClick={this.addToBasket} productsInBasket={this.state.productsInBasket}/> </div> ) } } const Basket = ({productCount}) => ( <div className="Basket">Basket ({productCount})</div> ) class AllProductsList extends Component { constructor () { super() this.state = { products: [{id: 21, name: 'Pikachu Mascot'}, {id: 7, name: 'Gloomhaven'}] } } render () { return ( <ProductList products={this.state.products} handleBuyClick={this.props.handleBuyClick} productsInBasket={this.props.productsInBasket}/> ) } } const ProductList = ({products, productsInBasket, handleBuyClick}) => ( products.map(({id, name}) => ( <Product key={id} id={id} name={name} count={productsInBasket[id] || 0} handleBuyClick={handleBuyClick}/> )) ) const Product = ({id, name, count, handleBuyClick}) => ( <div className="Product"> <h3>{name}</h3> <button onClick={() => handleBuyClick(id)}>Buy ({count} in basket)</button> </div> ) [/cc] In order for every instance of Product component to be able to add a product to basket when a button is clicked, we send it a handleBuyClick prop through AllProductsList and ProductList components. We also want to show in the Product component the number of pieces of a given product that are already in the basket. Because of that, we pass productsInBasket from App to AllProductsList and further to ProductList which then renders Product component instances sending them info about their amount in basket through count prop. We'd like to use the Inversion of Control strategy from the first example and instantiate Product in App where it could be given values for its handleBuyClick and count props, and be passed down the component tree to be rendered many times inside ProductList, reducing the need to pass these two props through AllProductsList and ProductList components. Let's try to do that: [cc lang="JavaScript" escaped="true" lines="100"] class App extends Component { //... render () { const product = // ooops, what to pass in place of question marks? <Product id={?} name={?} count={this.state.productsInBasket[?]} handleBuyClick={this.addToBasket}/> return ( <div className="App"> <Basket productCount={this.countProductsInBasket()}/> <AllProductsList render={product}/> </div> ) } } [/cc] The thing is, we can't do that. Every Product component instance also requires name and id props, but these details are not known in App - they're known in ProductList. What’s more, the count prop depends on id, too, so can’t be resolved inside App. The things known in App are this.addToBasket function and this.state.productsInBasket array which are also needed to provide `Product` with props, but don’t let us sastisfy all of them. Wouldn't it be nice if there would be a way to instantiate the Product partially and provide it with stuff to which App has direct access, and finish the instantiation in ProductList later, already having access to the right name and id? Well, there is a way to do that - you can use a render prop.

Render prop

A render prop is a prop which is a function that - received by a component - is executed by this component and its result is what the component renders. You can read more about render props in React docs. Here is how we could use it in our case: [cc lang="JavaScript" escaped="true" lines="100"] class App extends Component { //... render () { const product = (id, name) => <Product key={id} id={id} name={name} count={this.state.productsInBasket[id] || 0} handleBuyClick={this.addToBasket}/> return ( <div className="App"> <Basket productCount={this.countProductsInBasket()}/> <AllProductsList render={product}/> </div> ) } } class AllProductsList extends Component { //... render () { return ( <ProductList products={this.state.products} render={this.props.render}/> ) } } const ProductList = ({products, render}) => ( products.map(({id, name}) => ( render(id, name) )) ) const Product = ({id, name, count, handleBuyClick}) => ( <div className="Product"> <h3>{name}</h3> <button onClick={() => handleBuyClick(id)}>Buy ({count} in basket)</button> </div> ) [/cc] Instead of passing handleBuyClick and productsInBasket props, the intermediate components pass a single prop called render now (the name could be anything). App sets its value to be a function, accepting id and name parameters and returning a Product instance, provided with dependencies available inside App's render method. When the function reaches ProductList, the component executes it for every product received from "backend", passing the right arguments known at this level. The Product instance returned from the function now has all the props it requires. Thanks to this approach, we have limited the number of props passed through AllProductsList and ProductList components. The updated live version of our example using a render prop is available here: https://codesandbox.io/s/q8wj8mrqq9

Global variables

Almost every app has a piece of data it needs to read in numerous nodes of a component tree. This is especially annoying because to get the data to these nodes, we need to pass the same prop through the majority of components in the application, adding a lot of noise to its code. An example of such data is the id of the currently authenticated user or an application setting applicable globally (e.g., theme, locale). Another popular case would be a list of notifications added by many components. For such a list to work, we need to pass a function prop that would allow adding notifications to the list to all the components that would like to do that. Let's say we have an application like Reddit that shows different stuff posted by users. It makes use of react-router library and consists of a few pages: one of them shows the latest posts, another one presents the most upvoted posts (“top posts”), and the last one contains posts that got many upvotes only recently (let's call them "hot"). If the user is logged out, we only show 20% of the text in each post. Once they log in, a post’s content is presented in its entirety. The application also has a simple “contact” page, with a contact form and fake message sending code. If the user is logged in, their id is added to the message. Here’s a live demo: https://codesandbox.io/s/kmzyj915j5 And the source code: [cc lang="JavaScript" escaped="true" lines="100"] class App extends Component { constructor () { super() this.state = { userId: null } } logIn = () => { this.setState({userId: 15}) } logOut = () => { this.setState({userId: null}) } render () { const {userId} = this.state return ( <Router> <div className="App"> <Header showLogOut={userId !== null} handleLogInClick={this.logIn} handleLogOutClick={this.logOut}/> <Route exact path="/" render={() => <LatestPostList showExcerpts={userId === null}/>}/> <Route path="/hot" render={() => <HotPostList showExcerpts={userId === null}/>}/> <Route path="/top" render={() => <TopPostList showExcerpts={userId === null}/>}/> <Route path="/contact" render={() => <Contact userId={userId}/>}/> </div> </Router> ) } } const Header = ({showLogOut, handleLogInClick, handleLogOutClick}) => ( <div className="Header"> <Logo/> <HeaderMenu showLogOut={showLogOut} handleLogInClick={handleLogInClick} handleLogOutClick={handleLogOutClick}/> </div> ) const Logo = () => <div className="Logo">🌘</div> const HeaderMenu = ({showLogOut, handleLogOutClick, handleLogInClick}) => ( <ul className="HeaderMenu"> <li><Link to="/">Latest</Link></li> <li><Link to="/hot">Hot</Link></li> <li><Link to="/top">Top</Link></li> <li><Link to="/contact">Contact</Link></li> <li> {showLogOut ? ( <button onClick={handleLogOutClick}>Log out</button> ) : ( <button onClick={handleLogInClick}>Log in</button> )} </li> </ul> ) class LatestPostList extends Component { constructor () { super() // normally we would fetch latest posts from server this.state = { latestPosts: [{id: 185, content: 'Recent post 1'}, {id: 184, content: 'Recent post 2'}] } } render () { return <PostList posts={this.state.latestPosts} showExcerpts={this.props.showExcerpts}/> } } class HotPostList extends Component { // analogous to LatestPostList } class TopPostList extends Component { // analogous to LatestPostList } const PostList = ({posts, showExcerpts}) => ( posts.map(post => ( <Post key={post.id} content={post.content} showExcerpt={showExcerpts}/> )) ) const Post = ({content, showExcerpt}) => ( <div className="Post"> {showExcerpt ? content.substring(0, content.length * 0.2) + '... (log in to see all)' : content} </div> ) const Contact = ({userId}) => { let content = null return ( <form className="Contact" onSubmit={e => { e.preventDefault(); // fake message sending: console.log('Sending message: ' + content.value + ', from: ' + (userId !== null ? userId : 'guest')) }}> <textarea name="content" ref={input => content = input} /> <div><button type="submit">Send</button></div> </form> ) } [/cc] In the code above, if a user logs in, to simulate it the userId property in App's state is set to 15, and on logout it changes to null. HeaderMenu, Contact and Post instances depend on this data. How exactly? HeaderMenu accepts a showLogOut prop, being true if userId is not null, in order to render “Log out” button if somebody’s authenticated or “Log in” button otherwise. As for Contact, userId is passed to it directly and attached to the body of the message sent by the user. Finally, the Post components need to know if userId is null, to decide between showing an excerpt of a post or its full content. This information comes from App‘s state to each Post through intermediate components, in the form of showExcerpt prop. When App renders LatestPostList, HotPostList and TopPostList, it needs to tell all of them if userId is null or not, because each has a Post as a descendant and needs to transport that information to it. Ultimately, the user’s id and props derived from it reach nearly every component in the application. At the same time, similarly to the previous examples, only some of them really need it - the others only pass them down to children. We could use our Inversion of Control strategy again but instantiating all the components that depend on userId in App would quickly make it too complex, especially when adding new pages to our application (which are likely to also depend on userId). It still doesn’t mean you have to reach for Redux, though. For situations like this, React recommends a different tool - its Context API, rebooted this year and no longer depreciated. The Context API allows us to provide some data to all the descendants of a specific component that wish to consume it. Thanks to that we can avoid passing the data manually through numerous subtrees of our component hierarchy (like we did in the last example), which will lower the amount of noise but also make our data flow less explicit. In cases like the one in our example, it may be worth it. Let’s try to rebuild it using the Context API. Live demo: https://codesandbox.io/s/vxzvv1rol [cc lang="JavaScript" escaped="true" lines="100"] const UserContext = React.createContext({ userId: null, logIn: () => {}, logOut: () => {} }) class App extends Component { constructor () { super() this.state = { userId: null } } logIn = () => { this.setState({userId: 15}) } logOut = () => { this.setState({userId: null}) } render () { return ( <Router> <UserContext.Provider value={{ userId: this.state.userId, logIn: this.logIn, logOut: this.logOut }}> <div className="App"> <Header/> <Route exact path="/" component={LatestPostList}/> <Route path="/hot" component={HotPostList}/> <Route path="/top" component={TopPostList}/> <Route path="/contact" component={Contact}/> </div> </UserContext.Provider> </Router> ) } } const Header = () => ( <div className="Header"> <Logo/> <HeaderMenu/> </div> ) const Logo = () => <div className="Logo">🌘</div> const HeaderMenu = () => ( <UserContext.Consumer> {({userId, logIn, logOut}) => ( <ul className="HeaderMenu"> <li><Link to="/">Latest</Link></li> <li><Link to="/hot">Hot</Link></li> <li><Link to="/top">Top</Link></li> <li><Link to="/contact">Contact</Link></li> <li> {userId !== null ? ( <button onClick={logOut}>Log out</button> ) : ( <button onClick={logIn}>Log in</button> )} </li> </ul> )} </UserContext.Consumer> ) class LatestPostList extends Component { constructor () { super() // normally we would fetch latest posts from server this.state = { latestPosts: [{id: 185, content: 'Recent post 1'}, {id: 184, content: 'Recent post 2'}] } } render () { return <PostList posts={this.state.latestPosts}/> } } class HotPostList extends Component { // analogous to LatestPostList } class TopPostList extends Component { // analogous to LatestPostList } const PostList = ({posts}) => ( posts.map(post => ( <Post key={post.id} content={post.content}/> )) ) const Post = ({content}) => ( <UserContext.Consumer> {({userId}) => ( <div className="Post"> {userId === null ? content.substring(0, content.length * 0.2) + '... (log in to see all)' : content} </div> )} </UserContext.Consumer> ) const Contact = () => { let content = null return ( <UserContext.Consumer> {({userId}) => ( <form className="Contact" onSubmit={e => { e.preventDefault(); // fake message sending: console.log('Sending message: ' + content.value + ', from: ' + (userId !== null ? userId : 'guest')); }}> <textarea name="content" ref={input => content = input}/> <div> <button type="submit">Send</button> </div> </form> )} </UserContext.Consumer> ) } [/cc] As a first step, we create UserContext - an object that consists of Provider and Consumer components that we will use to pass data into the depths of the component tree, without threading it through all its levels. We use the UserContext.Provider component in App‘s render method - all the components wrapped in it will be able to consume the things passed to its value prop. Thanks to the Provider, App doesn’t have to manually pass userId to all of its children as it did before. The HeaderMenu component, which previously accepted three props, received from App through Header, now uses UserContext.Consumer in its render method, getting the three dependencies from the Consumer, and freeing Header from accepting any props. Post does a similar thing - gets userId it depends on through UserContext.Consumer, thanks to which (Latest/Top/Hot)PostList components don’t have to thread it through themselves anymore. The last thing depending on userId - the Contact component - also reads it from UserContext for consistency. By eliminating prop drilling of globally needed data, we have removed a lot of noise from the definitions of our components, at the cost of more implicit data passing code. In my opinion, in this case that was the way to go. This wraps it up. I’d like you to remember three things after reading the two parts of my article:
  • Prop-drilling is your friend - use it whenever you can until it becomes overwhelming; it makes your code easy to reason about.
  • When it gets unruly, reach for the Inversion of Control principle and instantiate components higher in the tree where the data they need is available, passing them to the place where they need to be rendered.
  • If that makes the top components too complex - for example, for data you need globally - use React Context.
  • And don’t forget about Redux, it’s an awesome library. Just use it in situations it was designed for.
  • If you have any questions, feel free to reach out to me in the comments section!

How to avoid prop-drilling in React without using Redux (Part 1)

Using Redux to manage your application state means that you’ll be dealing with a number of consequences - some of them beneficial and some detrimental when it comes to how comfortable the development process is. Here are some things you might not like that much:
  • Redux adds indirection to your state updates, which - in simple updates - makes it a bit harder to follow them,
  • It often results in moving state that properly belongs to a specific component (or component subtree) into the global store, hindering encapsulation,
  • You have to write more lines of code (though if you use Redux for a good reason and understand why you do it, you may have nothing against that).
The benefits of using Redux, on the other hand, are for example:
  • When testing your app manually, you can view all the dispatched actions and undo some of them to return to a certain moment of your test,
  • In case of a bug, you can attach a list of steps the user has taken to the bug report and replay their session,
  • Building collaborative applications (think Google Docs, for example) is easier,
  • Adding an undo feature is easier,
  • For complex state updates, the indirection Redux introduces may actually help in reasoning about them.
If you care about any of the above benefits and are ready to accept the downsides of Redux, feel free to adopt it. There are also some more reasons why people use it - but I think they’re not very good (even though they seem to be the most popular):
  • Ease of keeping the state between component remounts, or even between page reloads, because it exists in single global store,
  • The opportunity to avoid manual passing of data through many components in order to deliver it to one located deep in the component structure.
If you’re considering to use Redux for just one of the two reasons above, and not because of the previously mentioned benefits, there’s a good chance that you will be frustrated with its complexity. Redux wasn’t designed to solve these problems; you can find solutions that are far simpler. To achieve the former, you can alternatively use React Context API which also allows keeping the state you want to cache in a place where it won’t be lost on component remount and can be easily persisted in localStorage or a database. It also breaks component encapsulation, but doesn’t come with the indirection Redux introduces. In this article, I will focus on the second of these (questionable) reasons. I will describe the phenomenon of passing props through many levels of a component tree more thoroughly, try to evaluate whether that poses a problem, and explain how to limit it (when necessary) without using Redux, instead using Inversion of Control, render props and React Context API.

Let’s start with an example

We often work on applications where some data needs to be accessed from two or more components - sometimes residing far away from each other on the component tree. Let's have a look at an example with the following component structure (the arrows mark components that need to access the same data): [cc lang="JavaScript" escaped="true" lines="100"]
App Header HeaderTop HeaderBottom SidePanel SearchBar <-- AnimalPage AllAnimalsList <-- AnimalList
[/cc] You can see the source code (not using Redux) below. In this simple application, the term typed into the search bar is used to filter the list of animals. Both SearchBar and AllAnimalsList components need to access the search term. Because of that, we need to lift it up and put it in the state of their lowest common ancestor, which in our case is App. Here’s a live demo: https://codesandbox.io/s/k2vxxy53wv [cc lang="JavaScript" escaped="true" lines="100"]
class App extends Component { constructor () { super() this.state = { searchTerm: '' } } updateSearchTerm = (term) => { this.setState({searchTerm: term}) } render () { const {searchTerm} = this.state return ( <div className="App"> <Header searchTerm={searchTerm} handleInputChange={this.updateSearchTerm}/> <AnimalPage searchTerm={searchTerm}/> </div> ) } } const Header = ({searchTerm, handleInputChange}) => ( <div className="Header"> <HeaderTop/> <HeaderBottom searchTerm={searchTerm} handleInputChange={handleInputChange}/> </div> ) const HeaderTop = () => ( <div className="HeaderTop"> <div className="HeaderTop-logo">🌘</div> </div> ) const HeaderBottom = ({searchTerm, handleInputChange}) => ( <div className="HeaderBottom"> Welcome to our site! <SidePanel searchTerm={searchTerm} handleInputChange={handleInputChange}/> </div> ) const SidePanel = ({searchTerm, handleInputChange}) => ( <div className="SidePanel"> Search: <SearchBar searchTerm={searchTerm} handleInputChange={handleInputChange}/> </div> ) const SearchBar = ({searchTerm, handleInputChange}) => ( <input value={searchTerm} onChange={event => handleInputChange(event.target.value)}/> ) const AnimalPage = ({searchTerm}) => ( <div className="AnimalPage"> <AllAnimalsList searchTerm={searchTerm}/> </div> ) class AllAnimalsList extends Component { constructor () { super() this.state = { // normally, we would fetch this data from some backend animals: ['Dog', 'Cat', 'Duck', 'Chupacabra', 'Mouse', 'Human', 'Horse', 'Cow', 'Pig', 'Parrot'] } } render () { const filteredAnimals = this.state.animals.filter( animal => animal.toLowerCase().indexOf(this.props.searchTerm.toLowerCase()) !== -1 ) return <AnimalList animals={filteredAnimals}/> } } const AnimalList = ({animals}) => ( animals.map(animal => ( <div className="Animal">{animal}</div> )) )
[/cc] As you can see, to know the current search term and be able to modify it the SearchBar component accepts two props: searchTerm and handleInputChange. The values for these props need to be passed from App all the way down through Header, HeaderBottom and SidePanel components that don't use the props themselves. Their only concern with these two props is passing them further.

Prop-drilling

This phenomenon of passing props through components that don't directly use them was dubbed as "prop-drilling" by React community. Most people agree that it feels redundant and can be annoying to maintain. For example, if SearchBar needs one more prop, we have to add it to each of the intermediate components. If we change our mind and decide that SearchBar should now be rendered in HeaderTop instead, we would have to remove the two props from HeaderBottom and SidePanel, and add them to HeaderTop. Basically, wherever SearchBar moves, the props follow. In our case, it's not that much work but it can quickly become one with more props and more component nesting levels. In my experience, the desire to avoid prop-drilling is the most popular reason why developers reach for Redux. But consider this: Most of the time we actually want prop-drilling. Prop-drilling makes it clear where data comes from (by checking where searchTerm comes from) and who can change it (by checking where updateSearchTerm is passed to). Why is that important? Let's say searchTerm has an unexpected value. You need to check only the places to which updateSearchTerm is passed in order to find the code that sets it to an incorrect value. It's only sometimes that the number of props passed through many tree levels gets overwhelming. Our example is not one of these cases - it's simple enough to stay exactly in its current form. But let’s see how to reduce the number of passed props in case you experience a serious prop-drilling-related complexity in your project - and how to do it without Redux.

Reducing props passing without Redux

We could reduce the number of passed props by making use of the Inversion of Control software design principle. Basically, we want to instantiate SeachBar higher in the component tree and pass this instance as a prop, instead of searchTerm and handleInputChange props. As the first step, let's change HeaderBottom to instantiate SearchBar and pass it to SidePanel as a searchBar prop, which SidePanel just renders: [cc lang="JavaScript" escaped="true" lines="100"]
const HeaderBottom = ({searchTerm, handleInputChange}) => ( <div className="HeaderBottom"> Welcome to our site! <SidePanel searchBar={<SearchBar searchTerm={searchTerm} handleInputChange={handleInputChange}/>} /> </div> ) const SidePanel = ({searchBar}) => ( <div className="SidePanel"> Search: {searchBar} </div> )
[/cc] We have just reduced the number of props passed to SidePanel to one. Let's proceed to the second step and change HeaderBottom to be more generic, rendering anything it gets as a children prop. At the same time, let's move SearchBar to be instantiated even higher - in Header - and pass it, wrapped in SidePanel, to the new version of HeaderBottom: [cc lang="JavaScript" escaped="true" lines="100"]
const Header = ({searchTerm, handleInputChange}) => ( <div className="Header"> <HeaderTop/> <HeaderBottom> <SidePanel> <SearchBar searchTerm={searchTerm} handleInputChange={handleInputChange}/> </SidePanel> </HeaderBottom> </div> ) const HeaderBottom = ({children}) => ( <div className="HeaderBottom"> Welcome to our site! {children} </div> )
[/cc] Now we have limited the number of props passed to HeaderBottom component. We have also changed it to be more flexible, rendering whatever we pass to it as children (you can read more about this approach in React docs). Let's do the last step of our refactoring. We’re now going to instantiate SearchBar in App and pass it to Header as a searchBar prop. [cc lang="JavaScript" escaped="true" lines="100"]
class App extends Component { //... render () { const {searchTerm} = this.state return ( <div className="App"> <Header searchBar={<SearchBar searchTerm={searchTerm} handleInputChange={this.handleInputChange}/>} /> <AnimalPage searchTerm={searchTerm}/> </div> ) } } const Header = ({searchBar}) => ( <div className="Header"> <HeaderTop/> <HeaderBottom> <SidePanel> {searchBar} </SidePanel> </HeaderBottom> </div> )
[/cc] Note that we could have done it like this as well: [cc lang="JavaScript" escaped="true" lines="100"]
class App extends Component { //... render () { const {searchTerm} = this.state return ( <div className="App"> <Header headerBottom={ <HeaderBottom> <SidePanel> <SearchBar searchTerm={searchTerm} handleInputChange={this.handleInputChange}/> </SidePanel> </HeaderBottom> } /> <AnimalPage searchTerm={searchTerm}/> </div> ) } } const Header = ({headerBottom}) => ( <div className="Header"> <HeaderTop/> {headerBottom} </div> )
[/cc] That would be analogous to what we did in the second step with HeaderBottom, sending an entire subtree for it to render as a children prop. Here, we use headerBottom prop instead which feels more correct in this case as HeaderTop is also a child. Both versions we listed above may make sense, but in our case we will go with the first one. We don't want to give away control over what Header renders after HeaderTop. Instead, we always want Header to render HeaderBottom with SidePanel inside, and only let the parents decide what they will pass as searchBar. The resulting code is visible below. Check out the live demo: https://codesandbox.io/s/2xy1mv3rmp [cc lang="JavaScript" escaped="true" lines="100"]
class App extends Component { constructor () { super() this.state = { searchTerm: '' } } updateSearchTerm = (term) => { this.setState({searchTerm: term}) } render () { const {searchTerm} = this.state return ( <div className="App"> <Header searchBar={<SearchBar searchTerm={searchTerm} handleInputChange={this.updateSearchTerm}/>}/> <Page> <AllAnimalsList searchTerm={searchTerm}/> </Page> </div> ) } } const Header = ({searchBar}) => ( <div className="Header"> <HeaderTop/> <HeaderBottom> <SidePanel> {searchBar} </SidePanel> </HeaderBottom> </div> ) const HeaderTop = () => ( <div className="HeaderTop"> <div className="HeaderTop-logo">🌘</div> </div> ) const HeaderBottom = ({children}) => ( <div className="HeaderBottom"> Welcome to our site! {children} </div> ) const SidePanel = ({searchBar}) => ( <div className="SidePanel"> Search: {searchBar} </div> ) const SearchBar = ({searchTerm, handleInputChange}) => ( <input value={searchTerm} onChange={event => handleInputChange(event.target.value)}/> ) const Page = ({children}) => ( <div className="Page"> {children} </div> ) class AllAnimalsList extends Component { constructor () { super() this.state = { animals: ['Dog', 'Cat', 'Duck', 'Chupacabra', 'Mouse', 'Human', 'Horse', 'Cow', 'Pig', 'Parrot'] } } render () { const filteredAnimals = this.state.animals.filter( animal => animal.toLowerCase().indexOf(this.props.searchTerm.toLowerCase()) !== -1 ) return <AnimalList animals={filteredAnimals}/> } } const AnimalList = ({animals}) => ( animals.map(animal => ( <div className="Animal">{animal}</div> )) )
[/cc] After our change, only one prop is passed through each of Header, HeaderBottom and SidePanel components. If we needed to send two additional props to SearchBar, we would pass them directly to its instance in App. If we changed our mind and wanted to render SearchBar in HeaderTop instead, we would need to delete only one prop from SidePanel and add as little as one prop (e.g. searchBar) to HeaderBottom. Hurray! I will wrap up the first part of this blog post series here. In the second part, we will see how to reduce prop drilling in more complex situations using render props and React Context API. http://sunscrapers.com/blog/using-render-props-to-limit-prop-drilling/

Join our newsletter.