Web apps maintain state to enable more advanced user interactions. When we enter an email into a website, we expect that website to remember the email and not have to enter it over and over each time it displays. One way to remember information across renders in React is with state, specifically useContext hooks. I looked at useContexts and started using them in a project but ultimately went with component props instead. Here is what I learned in the process.
What are React contexts?
React context allows a state object to be read and written to form any component that imports the useContext hook and access to a provider in the component hierarchy. The state can be any collection of variables and functions. The provider is a React component that sets the initial state and allows all descendants to access the state. This simplicity makes React contexts flexible. It also allows for multiple different contexts within a web app depending on which components the app renders. You can modify the variables in a React context with functions that are a part of the state itself. Benefits of using React’s context include ease of importing and the ability to add the useContext hook to any component without modifying the props.
Where did we find problems?
We learned about useContext and started implementing it on our project to store information across all steps in a user workflow. We added the information the workflow needed and quickly saw the context state expanding with functions and variables. This on its own wasn’t a major issue. The state type was all in one file, which stayed readable.
The problem was that it became too tempting to modify variables that the current workflow step shouldn’t have had any power over. When a component imports useContext, it gains read and write access to the full state. The workflow developed hard-to-diagnose bugs that involved the state object being manipulated in unexpected ways. Within four weeks of development, the state was enabling interesting behaviors. We could click a button and changes would propagate through multiple steps and components. However, the code became difficult to maintain and understand, even on a small team where everyone had full knowledge of the codebase.
What did we do instead?
We decided to shift to a state object that is propped down from component to component. The benefit was that we could check at each layer to ensure the types only exposed the attributes and the functions that the component was interested in. This helped us keep the responsibilities of each component narrow and improved the readability of the code.
We found React Contexts enabled easy data sharing. However, this tool requires discipline to effectively control what is on the context and what can edit it. Did my team miss an opportunity by not making use of React contexts? Were the issues we bumped into a problem of our understanding or problems inherent in the tool? Let me know in the comments, and always experiment to find the right patterns for each project.