Manage complex state with React useReducer and useContext

This post will cover managing complex state at a feature level rather than the entire site. React hooks have enabled developers to have cleaner functional components which help to rationalise our component logic with easy.

Take useState hook, it’s one line of code that can be used to manage the state of a component rather than having to create a class component with the addition of boiler code. This is great because we are keeping simple things clear!

However, there are features that are inherently complex as they could have many nested child components and need to alter the state.

What optionsare there to manage this complexity in React ?

Photo by Ramón Salinero on Unsplash

Generally is good practice to keep it simple , which might lead you down the path of passing the setState callback down the stack of components. The downside here is the mutation of state can become difficult to follow, by allowing child components to alter the state directly will make it unclear what the state object should look like. By not having a single source of truth where the mutation is managed then the result could be unexpected and requires an overhead to work out.

It is also worth considering how easy is it to test changes to state. Unit testing state through rendering components can be tricky and takes more time to build compared to pure functions.

Ideally you want to make it easy to follow changes to the component state and create unit tests which give confidence in the functionality working.

To help explain the idea I’ve created a classic Todo app in CodeSandbox.io.

Since the release of React 16.8 hooks were introduced. A hook which is helpful for managing more complex state is useReducer and as the docs say it’s an alternative to useState . This essentially borrows the good concepts from Redux but with less complex setup and boiler code to work with React.

const [todoList, dispatch] = useReducer(toDoReducer, initialState);

We can also use React Context . The benefit of combining useReducer with context is being able to call the dispatch function anywhere down the component tree without passing through props. Preventing the need to follow it through the component tree to find the callback.

const TodosDispatch = React.createContext(null);

function App() {
  const [todoList, dispatch] = useReducer(toDoReducer, initialState);
  return (
    <TodosDispatch.Provider value={dispatch}>
     ...ToDoApp
    </TodosDispatch.Provider>
  );
}

To access the context in child components the React hook useContext can be used.

const dispatch = useContext(TodosDispatch);

This idea is recommended in the React docs, avoid passing callback down

Example React todo app with useReducer and useContext

Key pointsdemoed in the CodeSandbox

You can see how clean and simple the unit tests are for the reducer in the todo.spec.js file. This will give confidence that the logic works as expected when state changes. These unit tests help manage complexity preventing regression when the reducer is updated to handle a new action.

  • Key functions:
    • TodosContext is where the reducer dispatch callback will be stored
    • toDoReducer transforms the task list state based on actions e.g. add task
    • initialState contains one task object in an Array
    • addAction , markAction , deleteAction are all action creators which describe how to change the state
  • Key components:
    • App calls useReducer and passes the dispatch function into TodosDispatch.Provider
    • InputTask user can enter task name and calls the addAction on submit
    • TaskList reads the todoList state and renders a numbered task list
    • Action is a generic button component which has access to dispatch to trigger done, undo and delete actions.

Explore the CodeSandbox ( useReducer and useContext React todo app ) below to see how it’s all connected.

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章