Anti-Patterns to Be Avoided

In this book, you've learned how to apply best practices when writing a React application. In the first few chapters, we revisited the basic concepts to build a solid understanding, and then we took a leap into more advanced techniques in the following chapters.

You should now be able to build reusable components, make components communicate with each other, and optimize an application tree to get the best performance. However, developers make mistakes, and this chapter is all about the common anti-patterns we should avoid when using React.

Looking at common errors will help you to avoid them and will aid your understanding of how React works and how to build applications in the React way. For each problem, we will see an example that shows how to reproduce and solve it.

In this chapter, we will cover the following topics:

  • Initializing the state using properties
  • Using indexes as a key
  • Spreading properties on DOM elements

Technical requirements

To complete this chapter, you will need the following:

  • Node.js 12+
  • Visual Studio Code

You can find the code for this chapter in the book's GitHub repository: https://github.com/PacktPublishing/React-17-Design-Patterns-and-Best-Practices-Third-Edition/tree/main/Chapter13.

Initializing the state using properties

In this section, we will see how initializing the state using properties received from the parent is usually an anti-pattern. I have used the word usually because, as we will see, once we have it clear in our mind what the problems with this approach are, we might still decide to use it.

One of the best ways to learn something is by looking at the code, so we will start by creating a simple component with a + button to increment a counter.

The component is implemented using a class, as shown in the following snippet of code:

import { FC, useState } from 'react'

type Props = {
count: number
}

const Counter: FC<Props> = (props) => {}

export default Counter

Now, let's set our count state:

const [state, setState] = useState<any>(props.count)

The implementation of the click handler is pretty straightforward – we just add 1 to the current count value and store the resulting value back in state:

const handleClick = () => {
setState({ count: state.count + 1 })
}

Finally, we render and describe the output, which is composed of the current value of the count state, and the button to increment it:

return (
<div>
{state.count}
<button onClick={handleClick}>+</button>
</div>
)

Now, let's render this component, passing 1 as the count property:

<Counter count={1} />

It works as expected – each click on the + button increments the current value. So, what's the problem?

There are two main errors, which are outlined as follows:

  • We have a duplicated source of truth.
  • If the count property passed to the component changes, the state does not get updated.

If we inspect the Counter element using the React DevTools, we notice that Props and State hold a similar value:

<Counter>
Props
count: 1
State
count: 1

This makes it unclear which is the current and trustworthy value to use inside the component and to display to the user.

Even worse, clicking + once makes the values diverge. An example of this divergence is shown in the following code:

<Counter>
Props
  count: 1
State
  count: 2

At this point, we can assume that the second value represents the current count, but this is not explicit and can lead to unexpected behaviors, or wrong values down in the tree.

The second problem centers on how the class is created and instantiated by React. The useState function of the component gets called only once when the component is created.

In our Counter component, we read the value of the count property and we store it in the state. If the value of that property changes during the life cycle of the application (let's say it becomes 10), the Counter component will never use the new value, because it has already been initialized. This puts the component in an inconsistent state, which is not optimal and hard to debug.

What if we really want to use the prop's value to initialize the component, and we know for sure that the value does not change in the future?

In that case, it's best practice to make it explicit and give the property a name that makes your intentions clear, such as initialCount. For example, let's say we change the prop declaration of the Counter component in the following way:

type Props = {
initialCount: number
}

const Counter: FC<Props> = (props) => {
const [count, setState] = useState<any>(props.initialCount)
...
}

If we use it like so, it is clear that the parent only has a way to initialize the counter, but any future values of the initialCount property will be ignored:

<Counter initialCount={1} />

In our next section, we are going to learn about keys.

Using indexes as a key

In Chapter 10, Improving the Performance of Your Applications, which talks about performance and the reconciler, we saw how we can help React figure out the shortest path to update the DOM by using the key prop.

The key property uniquely identifies an element in the DOM, and React uses it to check whether the element is new or whether it has to be updated when the component properties or state change.

Using keys is always a good idea and if you don't do it, React gives a warning in the console (in development mode). However, it is not simply a matter of using a key; sometimes, the value that we decide to use as a key can make a difference. In fact, using the wrong key can give us unexpected behaviors in some instances. In this section, we will see one of those instances.

Let's again create a List component, as shown here:

import { FC, useState } from 'react'

const List: FC = () => {

}

export default List

Then we define our state:

const [items, setItems] = useState(['foo', 'bar'])

The implementation of the click handler is slightly different from the previous one because in this case, we need to insert a new item at the top of the list:

const handleClick = () => { 
const newItems = items.slice()
newItems.unshift('baz')

setItems(newItems)
}

Finally, in render, we show the list and the + button to add the baz item at the top of the list:

return ( 
<div>
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>

<button onClick={handleClick}>+</button>
</div>
)

If you run the component inside the browser, you will not see any problems; clicking the + button inserts a new item at the top of the list. But let's do an experiment.

Let's change render in the following way, adding an input field near each item. We then use an input field because we can edit its content, making it easier to figure out the problem:

return ( 
<div>
<ul>
{items.map((item, index) => (
<li key={index}>
{item}
<input type="text" />
</li>
))}
</ul>
<button onClick={handleClick}>+</button>
</div>
)

If we run this component again in the browser, copy the values of the items in the input fields, and then click +, we will get unexpected behavior.

As shown in the following screenshot, the items shift down while the input elements remain in the same position, in such a way that their value does not match the value of the items anymore:

Running the component, clicking +, and checking the console should give us all the answers we need.

What we can see is that React, instead of inserting the new element on top, swaps the text of the two existing elements, and inserts the last item at the bottom as if it was new. The reason it does that is that we are using the index of the map function as the key.

In fact, the index always starts from 0, even if we push a new item to the top of the list, so React thinks that we changed the values of the existing two, and added a new element at index 2. The behavior is the same as it would have been without using the key property at all.

This is a very common pattern because we may think that providing any key is always the best solution, but it is not like that at all. The key has to be unique and stable, identifying one, and only one, item.

To solve this problem, we can, for example, use the value of the item if we expect it not to be repeated within the list, or create a unique identifier.

Spreading properties on DOM elements

There is a common practice that has recently been described as an anti-pattern by Dan Abramov; it also triggers a warning in the console when you do it in your React application.

It is a technique that is widely used in the community and I have personally seen it multiple times in real-world projects. We usually spread the properties to the elements to avoid writing every single one manually, which is shown as follows:

<Component {...props} />

This works very well and it gets transpiled into the following code by Babel:

_jsx(Component, props)

However, when we spread properties into a DOM element, we run the risk of adding unknown HTML attributes, which is bad practice.

The problem is not related only to the spread operator; passing non-standard properties one by one leads to the same issues and warnings. Since the spread operator hides the single properties we are spreading, it is even harder to figure out what we are passing to the element.

To see the warning in the console, a basic operation we can do is render the following component:

const Spread = () => <div foo="bar" />

The message we get looks like the following because the foo property is not valid for a div element:

Unknown prop `foo` on <div> tag. Remove this prop from the element

In this case, as we said, it is easy to figure out which attribute we are passing and remove it, but if we use the spread operator, as in the following example, we cannot control which properties are passed from the parent:

const Spread = props => <div {...props} />;

If we use the component in the following way, there are no issues:

<Spread className="foo" />

This, however, is not the case if we do something such as the following. React complains because we are applying a non-standard attribute to the DOM element:

<Spread foo="bar" className="baz" />

One solution we can use to solve this problem is to create a property called domProps that we can spread safely to the component because we are explicitly saying that it contains valid DOM properties.

For example, we can change the Spread component in the following way:

const Spread = props => <div {...props.domProps} />

We can then use it as follows:

<Spread foo="bar" domProps={{ className: 'baz' }} />

As we have seen many times with React, it's always good practice to be explicit.

Summary

Knowing all the best practices is always a good thing, but sometimes being aware of anti-patterns helps us avoid taking the wrong path. Most importantly, learning the reasons why some techniques are considered bad practice helps us understand how React works, and how we can use it effectively.

In this chapter, we covered four different ways of using components that can harm the performance and behavior of our web applications.

For each one of those, we used an example to reproduce the problem and supplied the changes to apply in order to fix the issue.

We learned why using properties to initialize the state can result in inconsistencies between the state and the properties. We also saw how using the wrong key attribute can produce bad effects on the reconciliation algorithm. Finally, we learned why spreading non-standard properties to DOM elements is considered an anti-pattern.

In the next chapter, we will look into deploying our React application to production.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.149.242