Managing state with useReducer

Redux is great for managing complex state across our app. It is a little heavy though if the state we are managing only exists within a single component. Obviously, we can manage these cases with setState (for class components) or useState (for function components). However, what if the state is complex? There may be lots of pieces of state and the state interactions may involve lots of steps with some of them being asynchronous. In this section, we'll explore an approach for managing these cases with the useReducer function in React. Our example will be contrived and simple but it will give us an understanding of this approach.

We are going to add a Like button to the Product page in our React shop. Users will be able to like a product several times. The Product component will keep track of the number of likes and the date and time of the last like in its state:

  1. We'll start by opening Product.tsx and creating an interface, before the Product component, for our state, containing the number of likes and the date of the last like:
interface ILikeState {
likes: number;
lastLike: Date | null;
}
  1. We'll create a variable to hold the initial state, also outside of Product:
const initialLikeState: ILikeState = {
likes: 0,
lastLike: null
};
  1. Let's now create a type for the action:
enum LikeActionTypes {
LIKE = "LIKE"
}

interface ILikeAction {
type: LikeActionTypes.LIKE;
now: Date;
}
  1. We'll also create a union type containing all the action types. In our example, we only have one action type but let's do this to understand an approach that scales: 
type LikeActions = ILikeAction;
  1. Inside the Product component, let's call the useReducer function in React to get our state and dispatch function:
const [state, dispatch]: [
ILikeState,
(action: ILikeAction) => void
] = React.useReducer(reducer, initialLikeState);

Let's break this down:

  • We pass into useReducer a function called reducer (which we haven't created yet).
  • We also pass into useReducer our initial state.
  • useReducer returns an array containing two elements. The first element is the current state and the second is a dispatch function to invoke an action.
  1. Let's refactor this line and destructure the state so that we can reference the pieces of state directly:
const [{ likes, lastLike }, dispatch]: [
ILikeState,
(action: ILikeAction) => void
] = React.useReducer(reducer, initialLikeState);
  1. At the bottom of the JSX in the Product component, let's add JSX to render how many likes we have and a button to add likes:
{!props.inBasket && (
<button onClick={handleAddClick}>Add to basket</button>
)}
<div className="like-container">
{likes > 0 && (
<div>{`I like this x ${likes}, last at ${lastLike}`}</div>
)}
<button onClick={handleLikeClick}>
{likes > 0 ? "Like again" : "Like"}
</button>
</div>
  1. Let's add the like-container CSS class we have just referenced into index.css:
.like-container {
margin-top: 20px;
}

.like-container button {
margin-top: 5px;
}
  1. Let's also implement the click handler on the Like button:
const handleLikeClick = () => {
dispatch({ type: LikeActionTypes.LIKE, now: new Date() });
};
  1. Our last task is to implement the reducer function outside the Product component, just under the LikeActions type:
const reducer = (state: ILikeState = initialLikeState, action: LikeActions) => {
switch (action.type) {
case LikeActionTypes.LIKE:
return { ...state, likes: state.likes + 1, lastLike: action.now };
}
return state;
};

If we try this out, we'll initially see a Like button after we navigate to the Product page. If we click it, the button text turns to Like again and a piece of text appears above it indicating how many likes there are and the last time it was liked.

This implementation feels very similar to implementing actions and reducers in a Redux store but this is all within a component. This is overkill for the example we have just been through but could prove useful where we need to manage lots more pieces of state.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.86.155