https://www.joshwcomeau.com/react/common-beginner-mistakes/ JoshWComeau * Latest * Posts * Goodies * Courses HomeTutorialsReact Common Beginner Mistakes with React Pitfalls, gotchas, and footguns, oh my! Table of Contents IntroductionEvaluating with zeroMutating stateNot generating keys Missing whitespaceAccessing state after changing itReturning multiple elementsFlipping from uncontrolled to controlledMissing style bracketsAsync effect functionDeveloping an intuition Introduction A couple years ago, I was teaching React at a local coding bootcamp, and I noticed that there were a handful of things that kept catching students off guard. People kept falling into the same pits! In this tutorial, we're going to explore 9 of the most common gotchas. You'll learn how to steer around them, and hopefully avoid a lot of frustration. In order to keep this blog post light and breezy, we won't dig too much into the reasons behind these gotchas. This is more of a quick reference. Intended audience This article is written for developers that are familiar with the basics of React, but are still pretty early in their journey. Link to this heading Evaluating with zero Alright, let's start with one of the most pervasive gotchas. I've seen this one "in the wild" on a handful of production apps! Take a look at the following setup: Our goal is to conditionally show a shopping list. If we have at least 1 item in the array, we should render a ShoppingList element. Otherwise, we shouldn't render anything. And yet, we wind up with a random 0 in the UI! This happens because items.length evaluates to 0. And since 0 is a falsy value in JavaScript, the && operator short-circuits, and the entire expression resolves to 0. It's effectively as if we had done this: jsx Unlike other falsy values ('', null, false, etc), the number 0 is a valid value in JSX. After all, there are plenty of scenarios in which we really do want to print the number 0! How to fix it: Our expression should use a "pure" boolean value (true /false): jsx items.length > 0 will always evaluate to either true or false, and so we'll never have any issues. Alternatively, we can use a ternary expression: jsx Both options are perfectly valid, and it comes down to personal taste. Link to this heading Mutating state Let's keep working with our shopping list example. Suppose we have the ability to add new items: The handleAddItem function is called whenever the user submits a new item. Unfortunately, it doesn't work! When we enter an item and submit the form, that item is not added to the shopping list. Here's the problem: we're violating maybe the most sacred rule in React. We're mutating state. Specifically, the problem is this line: jsx React relies on an state variable's identity to tell when the state has changed. When we push an item into an array, we aren't changing that array's identity, and so React can't tell that the value has changed. How to fix it: We need to create a brand new array. Here's how I'd do it: jsx Instead of modifying an existing array, I'm creating a new one from scratch. It includes all of the same items (courtesy of the ... spread syntax), as well as the newly-entered item. The distinction here is between editing an existing item, versus creating a new one. When we pass a value to a state-setter function like setCount, it needs to be a new entity. The same thing is true for objects: js Essentially, the ... syntax is a way to copy/paste all of the stuff from an array/object into a brand new entity. This ensures that everything works properly. Link to this heading Not generating keys Here's a warning you've likely seen before: Warning: Each child in a list should have a unique "key" prop. The most common way for this to happen is when mapping over data. Here's an example of this violation: Whenever we render an array of elements, we need to provide a bit of extra context to React, so that it can identify each item. Critically, this needs to be a unique identifier. Many online resources will suggest using the array index to solve this problem: js I don't think this is good advice. This approach will work sometimes, but it can cause some pretty big problems in other circumstances. As you gain a deeper understanding of how React works, you'll be able to tell whether it's fine or not on a case-by-case basis, but honestly, I think it's easier to solve the problem in a way which is always safe. That way, you never have to worry about it! Here's the plan: Whenever a new item is added to the list, we'll generate a unique ID for it: js crypto.randomUUID is a method built into the browser (it's not a third-party package). It's available in all major browsers. It has nothing to do with cryptocurrencies. This method generates a unique string, like d9bb3c4c-0459-48b9-a94c-7ca3963f7bd0. By dynamically generating an ID whenever the user submits the form, we're guaranteeing that each item in the shopping list has a unique ID. Here's how we'd apply it as the key: jsx Importantly, we want to generate the ID when the state is updated. We don't want to do this: jsx Generating it in the JSX like this will cause the key to change on every render. Whenever the key changes, React will destroy and re-create these elements, which can have a big negative impact on performance. This pattern -- generating the key when the data is first created -- can be applied to a wide range of situations. For example, here's how I'd create unique IDs when fetching data from a server: js Link to this heading Missing whitespace Here's a dastardly gotcha I see all the time on the web. Notice that the two sentences are all smushed together: []Annotated playground showing that there's a space missing between the two sentencesAnnotated playground showing that there's a space missing between the two sentences This happens because the JSX compiler (the tool that turns the JSX we write into browser-friendly JavaScript) can't really distinguish between grammatical whitespace, and the whitespace we add for indentation / code readability. How to fix it: we need to add an explicit space character between the text and the anchor tag: jsx One little pro-tip: if you use Prettier, it'll add these space characters for you automatically! Just be sure to let it do the formatting (don't pre-emptively split things onto multiple lines). Why hasn't the React team addressed this?? When I first learned of this strategy, it felt messy to me. Why can't the React team fix it, so that it works the way we expect?! I've since realized that there is no perfect solution to this problem. If React starts interpreting indentation as grammatical space, it solves this problem, but it introduces a slew of other issues. Ultimately, as hacky as it feels, I think this is the right decision. It's the least bad option! Link to this heading Accessing state after changing it This one catches everyone off-guard at some point or other. When I taught at a local coding bootcamp, I lost track of the number of times people came to me with this issue. Here's a minimal counter application: clicking on the button increments the count. See if you can spot the problem: After incrementing the count state variable, we're logging the value to the console. Curiously, it's logging the wrong value: Annotated screenshot of the playground, showing how the button holds the number 1, but the console logs the number 0 Here's the problem: state-setter function in React like setCount are asynchronous. This is the problematic code: js It's easy to mistakenly believe that setCount functions like assignment, as though it was equivalent to doing this: js This isn't how React is built though. When we call setCount, we're aren't re-assigning a variable. We're scheduling an update. It can take a while for us to fully wrap our heads around this idea, but here's something that might help it click: we can't reassign the count variable, because it's a constant! js So how do we fix this? Fortunately, we already know what this value should be. We need to capture it in a variable, so that we have access to it: js I like using the "next" prefix whenever I do stuff like this (nextCount, nextItems, nextEmail, etc). It makes it clearer to me that we're not updating the current value, we're scheduling the next value. Link to this heading Returning multiple elements Sometimes, a component needs to return multiple top-level elements. For example: We want our LabeledInput component to return two elements: a