When using the popular JavaScript library React there are some errors/problems that seem to pop up time and time again. They can be easily avoided in most situations and I would like to share them with you so you can spend less time debugging and more time writing code.
So let's not waste any time and take a look at our problems and how we can solve them ๐.
Content
- Forgetting to add keys with a list of elements
- Not returning a list correctly
- Not cleaning up certain
useEffect
side-effects - Not wrapping adjacent JSX elements
1) Forgetting to add keys with a list of elements
In React we often find ourselves with lists of data that we want to map into elements or components. This is often done using the Array.prototype.map
function to pass data from each index of the array to the element or component through props.
When we do this without adding a key
prop React will complain that each element is missing a key
. Essentially it is just a special attribute to which we pass a string. It should be a unique string which will distinguish it from its siblings that we are also mapping.
React says:
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity
Example Problem
Let's map some data before adding keys to our elements to see the problem in action. Here we will have a simple component that deconstructs foods
from the prop object. foods
is just an array of objects and it looks like this ๐
and our component ๐
and finally the warning from React ๐.
Solution
To fix this all we have to do is pass a unique key to the element we are returning. Often the data we are mapping comes from a fetch request and usually includes an id. Fortunately we have and id
property we can use from our set of data. Let's add our key
property.
If we didn't have a unique id we would need to have an alternative solution. Often people use the index of the array as the key
but this is not recommended for any set of data where positions in the list may change. It can negatively affect the state of the component. See here for more information Reactjs - Reconciliation.
Instead we could create our key by combining the name
property with the current date/time using the JavaScript Date
object.
2) Not returning a list correctly
To return or not to return
In React as we have already seen we are often iterating over some data . Perhaps we are filtering a data set down to a specific sub-set or mapping to the DOM. Whatever it is there are a few pitfalls we need to watch out for when it comes to returning our data otherwise we might be left scratching our heads.
Example Problem
A frustrating example can be seen when we map a data set to some elements or components. We expect to see the or elements on screen with the data we map into them. However we see nothing.
No error, no warning no data ๐คจ. In this situation it is likely that you're not returning your result correctly.
For our example we will be mapping our array of foods to some elements so we can show it to the user. It should look like this:
Instead our data will appear to be missing ๐.
Let me show you some examples where we won't see the output that we were expecting. We are passing our array to our component and destructuring it from the prop object the same as before.
Can you spot the problem below.
Correct! Here we are not returning anything either implicitly or explicitly using the return
keyword.
Let's take a look at another ๐.
This time we include the return
keyword but what we are actually doing here is returning undefined
. The code below the return statement never gets read.
There are other examples you might run into but let's take a look at the different solutions we can use.
Solution
Let's start with the explicit returns. If we move our article
element in line with the return statement all is well.
See below ๐
We could also wrap the return elements with parenthesis like this:
Another option is to return the result implicitly which means we can forget the return
statement and the function body curly braces. Check it out ๐.
or inline like this ๐.
The choice is up to you as long as you are aware of the possible pitfalls you encounter. If the data appears to be missing make sure you check your map function carefully and make sure you are actually returning correctly.
3) Not cleaning up certain useEffect
side-effects
The useEffect
hook allows us to perform side-effects inside functional components. Certain side-effects that we perform in this hook require cleanup. This means that when the component unmounts we can run a special function. Sometimes this is necessary otherwise we might see an error warning us of memory leaks in our apps.
Consider a useEffect
that performs some kind of asynchronous api call before setting some component state to the response. If the response is slow and the component unmounts before we receive a response then we might be trying to update the state of a component that is not mounted.
Example Problem
Let's take a look at two different situations where we might add some cleanup to our useEffect
.
The first is a situation is where we have an asynchronous fetch request inside our useEffect
hook. The user of the application navigates to a different page before we receive the response from the fetch call. This is our component before we add a cleanup function to theuseEffect
hook.
Here we are fetching some data after the component mounts and then using the result to set the components state. Finally we map the state to the DOM. Relatively straight forward ๐.
The second situation is where we add some eventListeners
to some DOM elements. If the component unmounts we are going to want to remove these listeners.
Check it out before we clean it up ๐
The logic inside our useEffect
is irrelevant for this simple example. All that matters is that we are adding an event listener and that will need to be cleaned up.
Solution
We begin by adding a cleanup function to our useEffect
like this:
It is simply a function that we add to the bottom of our useEffect
hook where we add our cleanup logic.
Now to cleanup our fetch request we can use the DOM api AbortController
which is available in JavaScript. It allows us to abort web requests which we will use to abort out of the fetch request whenever the component unmounts. Let's see it in action ๐.
First we create a controller by using the constructor with new AbortController()
and then we pass its property signal to the fetch request. This association of controller signal to the request allows us to abort the request by calling abort()
inside the cleanup function.
Now we are ensuring that we don't have any requests coming back to a component that is not mounted.
The cleanup function for our eventListener
example is simple which you might have already guessed. All we need to do is remove any listeners we create using removeEventListener
in our cleanup function. Let's see it in action ๐.
Hopefully now like me you won't forget to clean up your effects ๐.
4) Not wrapping adjacent JSX elements
This one is simple to debug but I though I would include it because I sometimes forget to do it until React starts shouting at me ๐ .
Adjacent JSX elements must be wrapped with an enclosing tag. There are a few different ways we can do this based on our requirements.
Example Problem
If we want the wrapper to be part of the DOM for structural purposes then go with some semantic element where possible (<article>
, <section>
etc.). I usually wouldn't recommend wrapping elements with a <div>
although it is fine if you want the wrapper for styling purposes.
Often we do not want the wrapper to part of the DOM because it serves no purpose there. We would only be adding markup only to shut React up.
Let's see the problem in action.
and the error it throws ๐
It is likely that your code editor gave you a warning about this before the error pops up which is great.
Solution
Fortunately React provides us with a solution. We can use React.Fragment
to wrap our adjacent JSX if we do not require the wrapper to be part of the DOM. Let's say this is the case for the following example.
First let's use React.Fragment
before we see how we can simplify it further.
If we don't need any attributes or keys for our fragment we can shorten React.Fragment
to this <>
empty tag. Have a look below.
Finally if we are mapping some data to JSX as we have previously seen then we need to add keys to our wrapping element. If all we have is adjacent JSX then we can wrap our elements with React.Fragment
and add a unique key to the fragment.
Conclusion
Thanks for getting this far! I hope you learned something from the article and now we can both make sure we avoid these problems in our future code.
If you enjoyed it feel free to drop a ๐ on the article. It inspires me to continue improving and make more awesome content.
If you would like to connect with me then come an say hi @Kieran6dev as I am always active in communicating with other devs over on Twitter.
Thanks!
Top comments (5)
You should not use nanoid, or any other random ID generator, to create keys for your React array elements. In fact, it says, right on their README, that
Using a random random generator for keys forces the the reconciliation process to always view every element in the array as "new" or "changed", which forces it to rerender - and that can cause some nasty side effects.
I was actually learned this the hard way several years ago when I was using my own custom random ID function with Material UI's Collapse component. You can see an explanation of the issue here:
stackoverflow.com/questions/513719...
That's good to know
You're right, not sure how I missed this. Removed it for now. Thanks for the input!
Years ago, I did the
return\n
error all the time...Great article, these are the little things that can cause big problems that can be hard to find and solve.
It's easy to make these errors at first ๐ . Thanks for sharing, I'm glad you liked it!