Replace redux with react context and useReducer

Redux is the most popular state management solution for react applications, it provides a way to store and reason about state in a consistent way.

You dispatch actions which signify a single data change:

const action = {
  type: "ADD_USER",
  data: "Jim",
};

You have a reducer function which process any actions dispatched along with the current state and computes the new state.

function reducer(state, action) {
  switch (action.type) {
    case "ADD_USER":
      return { ...state, user: action.data };
  }
}

This allows you to have your state in a central place, decoupling it from your views and making it available to any component regardless where they are in the component tree.

It also promotes immutability, it is difficult to ascertain wether an object has been modified or not, by making your data immutable and returning a new object when changes have been made makes it a simple compare to determine if the data has changed.

This is a simplified version of what redux does, the pattern is simple and easy to implement, and the redux browser tools are great for debugging what is happening in your app.

React hooks were introduced in React 16.8 and provided some apis to handle state in function components.

useState

useState is a react hook that allows you to store some state in a react function component across update calls.

const [state, setState] = useState(1);

A function component is called on every component update so useState provides a way to store and retrieve some state, you can pass in the initial state and it will return the state and a method to update the state.

Theoretically you could use this as your only method of storing state in your application but it will quickly become cumbersome to manage anything beyond a single piece of data.

useReducer

Is essentially the same as redux, you pass it your data reducer function which will handle your actions passed into the dispatch function.

const initialState = { count: 0 };

const [state, dispatch] = useReducer(
  function reducer(state, action) {
    switch (action.type) {
      case "INCREMENT":
        return { count: state.count++ };
    }
  },
  initialState,
  init
);

return (
  <div>
    {state.counter}
    <button onClick={() => dispatch({ type: "INCREMENT" })}>Increment</button>
  </div>
);

You can pass in an optional initial state and a function to lazy initialise state.

How do we make this available to the components in our component tree?

Context

Context is a convenience component that allows you pass an object to any child component in the tree without having to use component props. Context was previously an experimental api and will not be supported beyond 16.x, they have provided a useContext hook.

const DataContext = React.createContext(defaultValue);

function App() {
  return (
    <DataContext.Provider value={{ state, dispatch }}>
      <App />
    </DataContext.Provider>
  );
}

Place your provider in your app root, and useContext to access the value in any child component.

The default value is provided if the accessing component does not have a parent provider in the tree.

function Login() {
  const { state, dispatch } = useContext(DataContext);

  return <div>{state.user}</div>;
}

A popular and simple state management method without adding any additional dependencies, although you may still want to go with redux for the additional features it provides and its what you know.

© 2024 Timney.