This video is available to students only

The useReducer Hook

In this lesson, we'll configure the useQuery and useMutation Hooks we've created to use another state management Hook to handle state. We'll use React's useReducer Hook.

📝 This lesson's quiz can be found - here. 🗒️ Solutions for this lesson's quiz can be found - here. 📃 Grab a cheatsheet describing how React's useReducer Hook can be used - here.

Our custom useQuery and useMutation Hooks work the way we want them to. They return the data we expect from our GraphQL requests and return some status information such as the loading and error states of our requests.

If we take a look at how we're manipulating the state objects our Hooks are returning, we can see that we're using the useState Hook to achieve this. This is because useState is one of the primary Hooks given to us by React to manage the state of functional components.

For both the useQuery and useMutation Hooks, the state we're trying to manipulate and track is an object where each field of the object dictates something about the request. Every function in our fetch that sets the state can be seen to be an action of sorts.

  • The first action sets the loading status to true.

  • The second action sets the data in state to the data received.

  • The last action if ever to occur is to set the error status to true.

Since we have a clear pattern of actions that interact with the same object, we can instead use another state management Hook that React provides called useReducer.

useReducer#

We'll look to first use the useReducer Hook in our custom useQuery Hook, so we'll import the useReducer Hook from the react library in the useQuery.ts file.

useReducer behaves very similar to how Redux works.

Redux is a library that adapts the flux pattern to managing state in a client-side application.

useReducer takes the concepts of Redux and allows us to manage data with a similar pattern!

The useReducer Hook takes a reducer() function that receives the current state and an action, and returns the new state. useReducer returns an array of two values and can take three arguments:

  • The first argument is the reducer() function.

  • The second argument is the initial state.

  • The third (optional) argument is an initialization function responsible for initializing the state.

The useReducer Hook will appear more understandable when we start to implement it.

reducer#

In the useQuery.ts file, we'll define a simple reducer() function outside of our Hook. A reducer() function is a function that receives the current state and an action that would return the new state.

A switch statement is often used to determine the return value of state based on the action received.

The action parameter of the reducer function is to be an object that might contain a payload value we can use to update the state with. action is to usually contain a type property describing what kind of action is being made. action.type will be the expression used in the switch statement to evaluate the returned new state object in the reducer function.

By convention, action types are often denoted with capital letters. Let's specify the cases and the returns we expect our reducer to take for each action type. We'll specify three cases - FETCH, FETCH_SUCCESS, and FETCH_ERROR.

Though this should never happen, we'll also specify a default case in our switch statement that will throw an error if the action.type does not exist or match either the FETCH, FETCH_SUCCESS, or FETCH_ERROR types.

For each of the cases in our switch statement, we'd want the reducer to return a new updated state object. We have access to the initial state as the first argument of the reducer() function and to conform to the Redux/Flux pattern of how state should be treated immutable (i.e. can't be changed), we'll always return new state objects for each case.

Our useReducer Hook is to interact with a state object similar to what we had before. The state object contains the data, loading, and error fields.

For the first FETCH action that is to be fired, we simply want to make the loading field of state to true. We'll use the spread syntax to place the values of state in our new object and update the loading property to true.

In the FETCH_SUCCESS scenario, we'd want to update the data in our state as well as ensure loading and error are false. This new payload that we'd want to apply will arrive from the action itself.

In the FETCH_ERROR case, we'll want to ensure loading is false while error is set to true.

We've yet to specify the type values of the state and action parameters in our reducer() function. We've already created the State interface that describes the shape of the state object we want to interact with so we'll annotate the assign the state parameter in our reducer() function with the State interface type. We need access to the TData type variable that the State interface accepts so we'll say our reducer() function will receive a TData type variable as well.

Start a new discussion. All notification go to the author.