A Deep Dive Into the 3 React Pitfalls That Developers Fall Into

Tips to help you know if the code is good or… not so good

Alen Ajam
Better Programming

--

Accurate representation of the code you will see from time to time as a software developer

I have coached junior developers and got to know firsthand the challenges they usually face. So I have made a list of common mistakes that may be helpful to juniors just starting out or even those with a little bit more experience who wish to advance their knowledge!

1. Failing To Pass a Valid Key to Element Arrays2. Mutating the State3. Ending Up With Huge Components

All right, let’s get going.

1. Failing To Pass a Valid Key to Element Arrays

You might have already heard of this one: When mapping an array to turn it into one of React elements, you must provide each child with a unique key.

But why is that? And is it always the case? To answer that, we must first talk briefly about how React works under the hood.

Every time there’s a state or props update in your app, React runs its diffing algorithm, which compares the old DOM tree to the new one. This is how it learns what changed and, ultimately, what needs to be rerendered.

This means that it goes through every node and checks whether any parameter changed after the update, then the same process gets iterated on its children. Now let’s say we have those two trees:

Old tree on the top and new one on the bottom

As you can see, the update added a node on the bottom of the children: This is fine! React handles it well as it can understand that the first two nodes didn’t change, and there was a third new one. However, let’s take a look at a different scenario:

All three nodes are considered new

Here comes the trouble: The update added a new node on top of the children. When React compares each child sequentially, it never finds a match. This means it will also recreate those two children who didn’t change.

In this case, the children are just DOM elements, so you could say it’s not a big deal, but it can be much more of a performance issue when each child has a tree below it, and all of that needs to be re-created too. Or when there are many children to recreate.

Not only that! Since React cannot uniquely identify each child, there are some cases where it could mess things around and mix up the children’s state. Here’s an example you can look at: Write something in the first input and then “Add New to Start.”

So this is where keys come into play. Let’s take a look:

React understands there’s only one new node

Great. Through keys, React can now identify existing children, which are second and third, and it knows that the first child is late to the party. It won’t recreate existing children since it can match those correctly.

But what if I use the index as a key? Well, React does that by default. It will go with the array’s index until you provide an actual unique key. It will still warn you about the issue, so you can promptly handle it. So, why isn’t the index a valid key? Let’s see an example:

As you can see, the first child isn’t recognized as the new one because the key matches. However, its content changed, so React will recreate the node. The same goes for the other children. Furthermore, you may encounter the same state mix-up issues we discussed previously.

There’s one exception, though, where using the index should be fine, and that is when you are sure the children won’t get reordered. Still, I suggest you bind it to a unique id anyway and choose to use the index as a last resort.

Phew, that was a lot. Let’s move on to the next one!

2. Mutating the State

Again, I will explain the whys and hows on this one too. The previous point’s explanation gives us a head start.

As previously said, renders of an app are triggered by props and state updates. But how does React know when a state is updated? That’s our job! Each time we set a state through the intended API, React knows. For instance:

As long as you need to handle primitive data, things are pretty easy. They can, however, get messy when you have to deal with non-primitive data, and this is where I often see mistakes being made. Take a look at this:

What’s wrong here? We know we must use the setSandwich() function for React to trigger rerendering. But that’s not enough. We also have to make sure that we don’t mutate the state so that the old and new versions can be compared (Remember diffing?). A React state is solely immutable. This means you must overwrite its value each time you wish to change it.

React won’t see changes in this case since the mutation caused the two versions to be equal. Let’s say you display the sandwich content in a div: It won’t be updated. Hence you will still see the onions rendered (Yuck!).

So, how can we fix this? Let’s see the correct form below:

This is something very different. We set a new object which brings along the previous state’s values (notice how we get it from the setSandwich callback) but also overrides the onions property. Rendering will now work correctly since we instantiate a new object to set while keeping the old state untouched.

All right, let’s see an example on arrays:

Same story: Mutating the array prevents React from noticing its changes. Now let’s do it the right way:

Same principle: We set a newly instantiated array and keep the old one untouched. We are not done yet, though! For those of you great people who prefer TypeScript, I have a trick to help you avoid mistakes in the future.

As you can see, TypeScript comes with a handy ReadonlyArray type that omits all mutable properties we want to avoid. Now writing that push() will throw a TSLint error! There’s also the ReadonlyMap and ReadonlySet types which you can use in the same fashion.

3. Ending Up With Huge Components

You see this all the time. You need to make a small change. You open the target component, and 500 rows of code get thrown in your face. 80% of your effort was spent locating what you were looking for while trying not to break something else.

I will give you some tips on how to fix this and tidy up your components. First off, take your time. Think of it as writing an essay. You do your thing; you write everything down. Then, only when you’re done do you read it again and check for grammar mistakes and whatnot.

So when you start writing your component down, don’t try optimizing code right from the bat because you will end up redoing it a hundred times later.

Once you think the component is pretty much ready, you can look at what you’ve got and consider what could be improved. Here’s what you’re looking for:

Separation of Concerns. This is always a good rule of thumb. A component shouldn’t feel like it’s doing too many things. Does your component use a whole lot of hooks? Does your JSX exceed a couple of hundred rows? Then you probably have to break things down.

Try to break it into smaller parts visually. You should ask yourself, “Does this belong here?” or “Should my component care about knowing this?”. There isn’t one right way to go about this, so do what you feel is best and take it easy. Once you’ve done that, go ahead and break the code into smaller components.

Leveraging hooks. When I hop into a component, my main focus will be on what’s returned. That is, the JSX. That’s the main purpose of a component, to render some kind of UI.

Usually, though, to achieve the UI needed, we have to manage data coming from the backend, from a local storage, state and logic related to the UI (forms, navigation, styles, animations), and whatnot.

But we still have to keep our main focus on the JSX, right? My next suggestion is to keep everything else as minimal as you can inside your component.

Got some fetch requests in there? You might want to move those in separate files, as javascript functions or by making your own hook. Or, probably even better, you could leverage libraries like redux (with RTK query) or react-query.

Got any form of logic that probably involves quite the state management? Extrapolate everything related into a custom hook or give formik a try.

When managing complex state structures, you might want to consider useReducer() instead of useState(). This is another way you can extrapolate your state management and give you other benefits.

So you got the idea. I often enjoy making hooks or using someone else’s as you can get creative and do anything, all while keeping your components tidy and clean.

What I’m going to do now is give you a realistic example of this process, putting into practice most of the tips I just gave you:

This is the example component we start with

So this is the page for my fictional pizzeria. You can choose pizza and drink through the form, then submit your order. Fancy, huh?

Now bear in mind this is just an example. Hence the component isn’t that lengthy or complex, but we will still apply our principles just for the sake of demonstration. As you can see, we also have a header and footer coded together with the form, so we could definitely use some refactoring.

At the same time, both the form logic and fetch request are written right into the component, so we might also improve that.

Let’s see the result:

Like magic! It is much easier to understand what’s going on now

Here’s what I’ve done: First, I’ve moved the header, form, and footer into three separate components. Speaking of the form, the props you see allow it to speak with the parent component as before. So this was the easy part. Now, let’s take a look at the new hooks.

I have exported all of the logic into two distinct hooks: useForm() to do state and event management of the form, and useApi() to take the form submission and POST it. Here’s what they look like:

The useForm() hook handles the form’s value state and events
The useApi() hook provides the post function and returns the corresponding response

So I’ve separated the logic following the Separation of Concern principle. Other than that, the code is almost the same as before hence it wasn’t a costly refactoring task either.

All right, people, I think we can wrap this up. I hope it wasn’t too boring and that it taught you something new. Soon I will be writing an article dedicated to React hooks, so should you be interested in that

Thanks for reading, and stay tuned for more. See ya.

References

Cover Image

--

--