While we're doing this, we might as well refactor the action out of each individual input: Seriously, how clean and clear is that code? Each time the reducer() function updates the state, the component re-renders as a result and receives the new state. Personal Development as a Software Engineer, React component rendering a list of items, React State Hooks: useReducer, useState, useContext. Subscribe to my newsletter to get them right into your inbox. There's also a lot of libraries offering opinionated ways to manage your entire (or part of) state, like Redux, Mobx, Recoil or XState. Let's see them side by side: As you can see, in both cases the hook returns an array with two elements. You're welcome to write me an email message just to say thanks, refer me to a job, or with an interesting job proposal. Plain React in 200+ pages of learning material. Redux still does more than Context + useReducer combined it has the Redux DevTools for great debugging, and middleware for customizability, and a whole ecosystem of helper libraries. How does the action get in there? When you'd like to update the state, simply call dispatch(action) with the appropriate action object. Here is an example of useReducer in a counter app: This is just the logic to keep track of the todo complete status. Wiring all these terms together, here's how the state update using a reducer works. We will start with a React component rendering a list of items. Weve wrapped the input with a form so that pressing Enter will trigger the submit function (because thats how HTML forms work by default). Now that you know how useReducer works and how to use it in your components, we need to address an important question. A quick aside, and then well get back to the reducer, but I wanted to explain what useRef does. the reducer function) will be called 3 times: I spent half a page explaining Arrays reduce function because, well, useReducer takes the same arguments, and basically works the same way. A backend is usually structured with some way to persist data (a database) and an API that lets you modify the database. Insert a button above the
and give it an onClick prop that dispatches an action with type clear. The world of Hooks is a new world: its worth considering whether you find old patterns valuable and want to keep them, or whether youd rather change things up. I help developers understand Frontend technologies. Well add a delete next to the item, which will dispatch an action with type === "remove" and the index of the item to remove.
You might notice that we have one more place where we repeat ourselves in this code: the onChange event handler. Then we need to handle that action in the reducer, which well do by filtering the array to remove the doomed item. If you're unfamiliar with the syntax of the Action type, it's a discriminated union. Then React redirects the action object and the current state value to the reducer function. Get certifiedby completinga course today! We'll also learn about it in detail later in the article. In this post were looking at the useReducer hook. With useReducer, the logic is actually quite simple: Add a bit of rudimentary CSS to style the error class, and you have the beginning of an input with good UX and simple logic, thanks to useReducer: Another good use case for useReducer is when you have a LOT of different pieces of state, and putting them all in useState would get really out of hand. My daily routine consists of (but not limited to) drinking coffee, coding, writing, coaching, overcoming boredom . You have ESLint right? state. The captain's bridge has a special communication device called engine order telegraph (see the picture above). Tutorials on React and Javascript, updated weekly! If you have any difficulties understanding the reducer concept, please revisit the referenced tutorial from the beginning for Reducers in JavaScript. Will Redux get the hook? It's a minimal version of the useReducer hook to help you compare it with the backend mental model, but it lacks several important things that you'll learn about in this article. There are two hooks that are used for modern state management in React: useState and useReducer. useReducer Hook by adding more actions. You are probably already quite familiar with the former, so it's helpful to start there to understand useReducer. Sounds familiar! For example, we may want to add the touched and error property of the last section to each of the four inputs in this section. Each correct button press advances the state. This works for our purposes here, but its not a great idea for a real app because it could lead to duplicate IDs, and bugs. This solution works, and sometimes it's a good way to go. The object it returns will always have a current property, and we access the value inside the ref with inputRef.current. If you find yourself keeping track of multiple pieces of state that rely on complex logic, useReducer may be useful. If you've used useState() hook to manage non-trivial state like a list of items, where you need to add, update and remove items in the state, you might have noticed that the state management logic takes a good part of the component body. The bug is in the reducer: updating username will completely override the previous state and delete email (and updating email will do the same to username). Might as well use a reducer right away. Lets look at a complete useReducer example of a component using it to increment a number: You can see how clicking the button dispatches an action with a value of 1, which gets added to the current state, and then the component re-renders with the new (larger!) I send an article every Wednesday to help you level up as a front-end React developer. It might sound a bit strange, but bear with me: I'm very happy with this analogy and I think it explains reducers well. The useReducer hook is used for complex state and state transitions. If youre familiar with React.createRef(), this works very much the same. If the state has been updated, React re-renders the component and useReducer() returns the new state value: [newState, ] = useReducer(). Remember that the code above shouldn't be used in production. As a nice bonus, you will find in the post a real-world example that greatly helps undersanding how reducers work. Well: Here's a complete example of useReducer usage. The initial state is the value the state is initialized with. The stopwatch has 3 buttons: Start, Stop and Reset, and has a number displaying the passed seconds. It's good to know that useReducer has an optional third argument. In either case, you update the state by calling the update function (setState or dispatch) with information on how exactly you want to update the state. One email every week-ish. The useRef hook allows you to create a persistent ref to a DOM node, or really to any value. Some of those are things you should really be doing, others are more matters of personal taste. Whether the input has already been "touched" by the user. If you're a bit fuzzy about that last paragraph, I've got you covered with this article on dependency arrays! The first is the state, and the second is a function that lets you modify the state: setState for useState, and dispatch for useReducer. The useReducer() hook in React lets you separate the state management from the rendering logic of the component. This can be achieved easily with the spread syntax: This example can actually be optimized further. The dispatch function is created for your by the useReducer() hook: Whenever you want to update the state (usually from an event handler or after completing a fetch request), you simply call the dispatch function with the appropriate action object: dispatch(actionObject). The engine order telegraph helps separate the bridge from the engine room. The code below has a bug. Imagine doing this with 4 useState instead! Afterward, the React component is rendered again but using the new state from the useReducer hook. The word reducer might evoke images of Redux but I promise you dont have to understand Redux to read this post, or to use the new useReducer hook that comes with the React 16.8. As a result of an event handler or after completing a fetch request, you call the dispatch function with the action object. Now lets add the ability to remove an item from the list. Weve filled out the reducer function with two cases: one for when the action has type === 'add', and the default case for everything else. Here are a few tiny apps you can build to try out the useReducer hook on your own: Stay on top of React with a new post each week. As an additional payload, an identifier is needed which comes from the incoming action's payload. Typical commands would be to move back slowly, move ahead half power, stop, etc. (Beware! The hook does so by extracting the state management out of the component. Calling useRef creates an empty ref object by default, or you can initialize it to some other value by passing an argument. Depending on the action object, the reducer function must update the state in an immutable manner, and return the new state. These kinds of questions are always a tricky topic because the answer will usually change depending on who you ask, and it's also highly context-dependent. React then checks whether the new state differs from the previous one. We're closing on the end of our useReducer guide (phew, it turned out way longer than I anticipated!). Did you scroll down to just read the answer? Were using the length of the array as a sort of auto-incrementing (ish) ID. Your reducer receives the current state and an action, and returns the new state. Making it scalable and performant is harder, though. The useReducer(reducer, initialState) hook accept 2 arguments: the reducer function and the initial state. React's useReducer hook is a powerful way to manage state in React. Now let's think about structuring the state of the stopwatch. Especially if you've never worked with similar structures before. For example, if the field is required you want to display an error when it's empty. The main difference in the hook arguments is the reducer provided to useReducer. GET requests let you access the data, and POST requests let you modify it. When the reducer gets the add action, it returns a new array that includes all the old elements, plus the new one at the end. So conventionally, the action is an object with one required property and one optional property: In our previous example of a counter, state was an object with a single count property. ESLint (rightly) wants switch statements to have a default case. action is an object whose type can be 'increment', and whose payload is the amount by which you want to increment the counter. useReducer is localized to a specific component. I dont think useReducer will kill Redux any more than Context killed Redux (it didnt). Learn React like 50.000+ readers. That's a problem because the React component in nature should contain the logic that calculates the output. But the state management In our counter example the initial state was simply: We'll see more examples of this further down the road. Since useState and useReducer are two ways of managing state, which should you choose when? Some people like to simply return the state: But I really don't like that. It can hold any value specific to this component instance, and it persists between renders. We'll then go over a useState vs useReducer comparison to learn when to use which. What this does is call the function for each element of the array, passing in the previous total and the current element number. In our counter example we had a switch statement with three cases: "increment", "decrement" and "reset". logic is a different concern that should be managed in a separate place.
Then, later, you can access the DOM node through theRefVariable.current. Let's see how this would look in a React component. But you'll often find yourself re-implementing a useReducer in a more awkward way. You're on the bridge and the ship is at full stop. Either the action type is something you expect and should have a case for, or it's not, and returning the state is not what you want. Tutorials, references, and examples are constantly reviewed to avoid errors, but we cannot warrant full correctness of all content. It's commonly an object, and you shouldn't be adding new properties to that object inside your reducer. Now need to write the handleSubmit function that will add an item to the list, as well as handle the action in the reducer. For example, in the case of a counter state, the initial value could be: An action object is an object that describes how to update the state. What I mean by "conventional action structure" is the structure we've been using so far in this article: action should be an object literal with a required type and an optional payload. This is the same behaviour as the setState function from useState. It's really all it is. We can easily scale that example up to 4 inputs. In this example, the function provided to reduce (a.k.a. Now that (hopefully) you have a good idea of how useReducer is working on a high level, let's explore the details further. You can pretty safely argue that Redux is used in plenty of places where it is overkill (including almost every example that teaches how to use it, mine included! The second argument to reduce (0 in this case) is the initial value for total. Were also clearing out the input. This is a very simple example, and real-life reducers are usually much bigger and more complex. And indeed, you could further extract that into a reusable action to which you only have to provide the key. This communication tool is used to transmit commands from the bridge to the engine room. The handleSubmit function is called when the user presses Enter in the input box, and we need to call preventDefault to avoid a full page reload. If you haven't used state management excessively in React Function Components , this tutorial may help you to get a better understanding of how React Hooks -- such as useState, useReducer, and, Since React Hooks have been released, function components can use state and side-effects. This. If you're a TypeScript user, you're probably wondering how to properly make the two play nice. And if you want to do that, and it fits your needs, go for it! We saw earlier an example of a single reducer managing 2 inputs with the same action. An initial state is provided both for useState and useReducer. Otherwise, you get a mix of state management and rendering logic in one place, When you do a POST request you can also give some parameters; for example if you want to create a new user you'll typically include the username, email and password of that new user in the HTTP POST request. You might have noticed that we are repeating ourselves a bit in the reducer: both the username and email cases have essentially the same logic. Where is the (magic?) In this app, our actions are objects with a type property and some associated data, but they can be anything you want (plain strings, numbers, more complex objects, etc). The official Hooks FAQ has an example of using a ref as an instance variable. One way to think about useReducer is to think of it as a backend. Also, the reducer returns an array of 2 items: the current state and the dispatch function. (We could also manage the input with state, passing the value and onChange props as usual, but this is a good chance to show off the useRef hook!). As result, here's how the initial state can look like: The initial state indicates that the stopwatch starts as inactive and at 0 seconds. By convention, that argument is an object with a type and an optional payload, as we saw in the last section. The ref will hold a reference to a form input, so that we can extract its value. It can be used with useState and useContext for modern state management in React. For instance, if you have an array of numbers and you want to get the sum, you can write a reducer function and pass it to reduce, like this: If you havent seen this before it might look a bit cryptic. For simple state management, simply use useState(). The same way the useReducer() hook helps separate the rendering from the state management logic. React will persist this value between re-renders (between calls to your component function). A Simple Explanation of React.useEffect(), The Complete Guide to useRef() and Refs in React, A Simple Explanation of JavaScript Closures, Gentle Explanation of "this" in JavaScript, 5 Differences Between Arrow and Regular Functions, 5 Best Practices to Write Quality JavaScript Variables, 4 Best Practices to Write Quality JavaScript Modules, 5 Best Practices to Write Quality Arrow Functions, Important JavaScript concepts explained in simple words, Software design and good coding practices.