Our JavaScript man-in-the-know Kevin explores some of the common gotchas that can catch React developers unawares...
01-09-2021
As a freelance developer and teacher, I am in the privileged position of getting to see a lot of React code in the wild. I help support teams as they learn the framework and come to terms with its quirks. I also get to work with codebases that have grown over time. Here are some of the common mistakes that can stop applications being the best they can be.
1. Not enough components
React gives us a lot of freedom in how we structure and organise our applications. It allows us to create a whole application in a single component if we want to. But, as Uncle Ben has told us, with great power comes great responsibility.
A single, large and complex component can be hard to grasp and understand. It can slow down debugging efforts and make extending your application more challenging. Having smaller, single function components allows for cleaner and more readable code. We can combine (or compose) these smaller functions. In this way, we can create complex interfaces out of simpler building blocks.
This can reduce the amount of copy and pasting in your codebase. Over time, you'll build your own component library, which makes it easier to be consistent with your styling. Additionally, it makes it easier to reason about the implications of any code change.
2. Writing logic in components
React is an amazing front-end framework. With it, we can achieve great things! With backends powered by Node.js or Deno, we can code with JavaScript on both the front and back end.
One thing I've noticed in teams like this is the temptation not to separate your logic. When you can write your logic anywhere, it can be a challenge to decide where to put it. If we're focused on building smaller, reusable components then this is less of an issue. We stop ourselves adding business logic to our components and focus on the reusable UI. Use helper or utility functions or, even better, have your backend do all the heavy lifting. The React development team work hard to optimise the framework - let's lean in to its strengths!
3. Modifying the state directly
Oh, State! How we love you! How we hate you! For me, state is one of the most interesting topics in development. I love how solutions from decades ago are as relevant today. The next few mistakes are all about State. For those of us arriving to React-land from other part of JavaScript, it can be tempting to change state in place.
Here, we're using React's useState hook to initialise a state variable as an object. The only property we've set is the name but this could be a more complex object. We add an onClick handler that calls the updateName function. This updates the name object and then logs the new value. In the console, everything seems to go fine but the component doesn't rerender. Our underlying data has changed but the UI isn't kept in sync. Instead, we need to use the setter function we get from the useState array.
That fixes our UI but it raises another mistake that is often made in React development. In development though, a new error is a sign of progress in my book!
4. Forgetting that setState is async
Running the code above and clicking the button causes a problem. If you haven't spotted it head over here, click the button and check the console. Did you spot it? Even as the UI updates to show the new name of Kevin, the logged value to the console has a name property of John.
This happens because setState is asynchronous. We can't be sure what the value of state will be directly after we set it. React can batch the state calls which can lead to nasty bugs in this instance. A better approach, would be to deploy a useEffect to handle the logging.
My favourite React hook is useEffect! I love how it simplifies so much legacy code by stripping away the need for lifecycle methods. In this instance, our function logs on the first render and then every time person updates. Perfect!
5. Using useState when no re-render is required
React's useState hook is great. The more we use it, the more we reach for it. It's good to remember that no matter how good our hammer is, not everything is a nail. If you are creating or updating a variable which isn't going to trigger a re-render then use the JavaScript language. It's good to remember that the purpose of this hook is to update the UI based on the data. If the UI isn't dependent on this data, then you can and should use variables outside of React.
6. Not using multiple useEffects
To be able to log our value in a useful way above, we made use of a useEffect hook. The dependency array at the end of the useEffect tells React when it should run the code inside. If it is empty, it will only run on mount making it resemble the old componentDidMount method. We aren't limited to a single instance of this hook. The useEffect hook lets us split the code based on what it is doing rather than the lifecycle method.
This separation stops unnecessary calculations. We shouldn't update the online status if the count changes and we shouldn't change the title when the status changes. There isn't much of a saving in this example but for more complex operations this is an effective optimisation.
7. Using onClick to trigger navigation
It is possible to put an onClick handler on a button and use that to push to the Router history. This is how it would look using React Router:
function NavButton({route, label}) {
const history = useHistory()
const onClick = () => {
history.push(route);
};
return <button onClick={onClick}>{label}</button>
}
This might feel fine but there are problems. The first is an accessibility issue. The onward navigation is hidden inside the button's functionality and so a screen reader is going to find it impossible to deal find. Secondly, hovering over this button won't show the link in the bottom of left of the browser. This missing hint can be confusing for users. Instead, you should use a Link component to trigger navigation.
function NavButton({route, label}) {
return <Link to={route}>{label}</Link>
}
8. Not using KEY on a listing component
Rendering lists of data is one of the most common uses of React in the wild. Whether that is components based on track listing, member lists or car types, the pattern is generally the same.
cars.map((car) => <CarComponent data={car} />)
If you've written code like this before, you'll have seen the following warning in your console:
Each child in a list should have a unique "key" prop.
This key needs to be unique amount the components siblings. The key helps React identify which items have changed, are added or are removed. In some use cases, if you know the data isn't going to change during the render, it might be enough to use the index of the array.
9. Forgetting to start a component name with a capital letter
I put this one in because I've helped a few people debug issues along these lines. In order for React to recognise a JSX component, a function component must begin with a capital letter.
function carComponent() {
return ...
}
This is to separate React components from native HTML elements and components.
function CarComponent() {
...
} function Table() {
return <table> ... </table>
}
We can implement our own Table component using the native table functionality under-thehood. This PascalCasing is how React allows us to maintain this difference.
10. Not writing Unit Tests
There is a developed testing eco-system that exists within React. My tool of choice is the React Testing Library which is build on top of the DOM Testing Library. This works with projects created with Create React App out of the box. This library lets us write and maintain tests for your components. These automated tests give you confidence that your code works for your users as you intend. The library provides a range of light utilities that help encourage testing best practices. This includes, as much as possible, excluding implementation details from your tests.
There is a maxim in this community. The closer your tests are to real user interactions, the more you can trust them. Having a test suite allows for faster and more reliable development and deployment.
11. Not using React dev tools
The React team have worked hard to provide developer tools in all the major browsers. When you use these tools, you can see how data is flowing through your application. It is possible to see how React has rendered your application and get an insight into sources of bugs. I know I'm a fan of a good logging statement. These are so useful - to confirm the value of a property, the result of a function or even if a block of code is called. With the dev tools though, we have these abilities but with great enhancements. We can optimise and debug much faster when we have a clearer mental model of what React is doing. The dev tools help us develop and maintain this model.
To summarise...
React is an amazing framework to work with. With this tool, there are so many companies that are doing great things. If you can avoid these mistakes your applications are going to be more resilient, reliable and effective
Want to learn more about React?
If you found this article interesting and useful, take a look at our React courses:
To help raise awareness of challenges and vulnerabilities and ways to reduce risk, we've got a bumper crop of cyber security blog articles. We've also got a robust range of hands-on training courses covering security for non-technical staff and IT professionals
We use cookies on our website to provide you with the best user experience. If you're happy with this please continue to use the site as normal. For more information please see our Privacy Policy.