Context

The official Context API was added to React in v16.3 and is intended to avoid what is commonly known as prop drilling, or manually passing props down a component tree.

While there is nothing wrong with this approach, it can be unwieldy when dealing with complex component hierarchies, especially if some components don’t care about the data and are simply passing it on.

In order to use the Context API, you need to create a Context.Provider that takes a value prop representing all the data you want to inject into the child components that need it.

Before the advent of Hooks, you would do this by using an associated Context.Consumer, but in the post-Hooks world, we can leverage the useContext Hook to subscribe a component to the closest Provider above it in the component tree.

In this case, Context provides a way to keep track of which menu should be open in our parent component, and then passes that value down to its children who, in turn, conditionally render the appropriate dropdown.

The key here is that our Context also passes down a setter function. This is important because it allows the components consuming our context value to then update the state in our parent component, which causes the tree to re-render with the new menu now visible.

By using the pattern described above, we can similarly manage the focus state of the various inputs in the search bar and filter menus.

Though our use case requires us to go down this road, it is important to note that refs are a React antipattern because they allow direct DOM access. React does not really intend for developers to do this, so when using refs you should proceed with caution.

Alternatively, React is designed to have events update the virtual DOM (a snapshot of the document object model kept in memory,) and allow the framework to update the page as needed in a process known as reconciliation.

This not only makes React more performant by reducing the work done to update the page, but it also makes for a more consistent user experience.

For us, we need to directly access the three inputs in our component tree in order to use the browser’s .focus() method. Since we need to change focus states for our inputs based on user interaction, it makes sense to keep this logic in our parent component, too.

We’ll create three different refs in our parent component that will point to the following:

The input in our search bar

The input in our first filter menu (ex. Author)

The input in our second filter menu (ex. Year)

Using our Context, we can then pass these refs down to our child components. In the child component, we destructure the appropriate ref off the component’s props object and assign it directly to our HTML input:

Conclusion

While Context is a relatively new API and refs are somewhat of an antipattern, they compliment each other well in this case. By combining the two together, we are able to create a more straightforward way of managing both display and focus states within our new search interface.

While we didn’t use any stores in this example, you could easily wire one up and include it in this data flow.

For example, in the project I built at work, my parent component was subscribed to a store that provided a list of the items to be rendered in our dropdown menus.

Once the parent received this data, it added it to our Context’s value prop and passed it down to the child components.

Ultimately, the ability to centrally locate logic in this way allows for a more consistent user experience.

By using Context, we easily decoupled our business logic and data fetching from the presentation and functionality of our UI components.

We also made our code easier for other developers to read in the future, which is never a bad thing!

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.