trivago tech tips - #24 React Edition
In 2020, we started using React for our main product. Since then, it's been widely used within trivago for several different purposes.
Here are some of the cool tips that we’ve discovered while using it! 😉
Tip #1: Give meaningful names to your components properties
The names of your components properties should be descriptive. As much as possible, they should indicate how the component will look like in the UI. They should not describe in which context the component is mounted.
This will become very helpful, especially for re-usable components, where with a quick look at the properties, you can already know how the component will look like or behave.
For example, let’s say you have a Banner
component that is used on the home page of your app and also on other pages. You want the Banner
component on the home page to be fancy, big, and blinking for some reason, but for now only on the home page.
You may be tempted to create a isOnHomepage
property for your component such as:
<Banner isOnHomepage={true} />
And then checking for this property in Banner
, and if it’s true
apply the styles to make it big and blink. However there are problems with this approach:
The property is not descriptive. We can’t know how the component will be like just by looking at the property.
What if we want to make this component big and blinking on other pages of the app in the future?
What if we want two
Banner
components on the same page, one being big and blinking, and the other one being not?
A better approach, and a solution to these problems above, would be to make the properties more descriptive, for example:
<Banner isBig={true} isBlinking={true} />
This solves the problems described above. You can now guess how the component will look like, and it gives a better control over the component.
Tip #2: Explicit values are not always needed for Boolean properties
Another small tip related to properties and to the first topic above; if the values of your components properties are Boolean, you don’t need to explicitly pass them.
When a Boolean property is true
, it’s possible to only pass the property name. For example:
<Banner isBig isBlinking />
Will be the same as:
<Banner isBig={true} isBlinking={true} />
Now, what if the value of property is false
? Then, you could disregard the property. For example:
<Banner />
To be noted: in this case, inside the Banner
component, isBig
and isBlinking
won’t be set to false
. They will be undefined
. So you will either need to check if they are defined (and assume that they are false
if not defined), or you can set them to false
by default.
Tip #3: Don’t hesitate to use default values for your components properties
Related to our previous tip, don’t hesitate to take advantage of default values for your properties instead of checking if they are defined right before using them. It will make your code much more readable, and you will be able to skip many conditions.
For example, with our Banner
example above, and the properties isBig
and isBlinking
, the interface of our component could look like this:
const Banner = ({ isBig = false, isBlinking = false }) =>{
....
};
This way you can rest assured that when your component was mounted as:
<Banner />
The isBig
and isBlinking
properties will be false
and not undefined
.
This might not be a big deal for Boolean values as the values are also falsy when they are undefined
. But where it shines is when passing functions in your component properties. For example let’s say that, in some cases, we want to do something when the users click on the banner. We would add a new onClick
property:
<Banner onClick={() => console.log('clicked!')} />
Now, what if we are not interested in reacting to a user click? And we mount our component without passing any onClick
property such as:
<Banner />
Without a default value for the onClick
property, in our component definition, before calling the onClick
function, we will first need to check if onClick
is defined:
const Banner = ({ onClick }) => {
return (
<button onClick={onClick && onClick()}>Click me!</button>
);
};
You’ll notice that here we need to write onClick && onClick()
, simply because we need to check that onClick
is defined before calling it.
Now, we could take advantage of passing a default value for the onClick
property. We could set it as an empty function, so that when calling it, it would not do anything if the property is not defined.
const Banner = ({ onClick = () => {} }) => {
return (
<button onClick={onClick}>Click me!</button>
);
};
This way, we don’t have to check anymore if onClick
is undefined
before calling it.
Tip #4: Prefer unique IDs over indexes for the value of the key property in loops
When rendering a list of children, setting a key
property on each child is important since it helps React to identify them, and it avoids mutating the children that didn’t change between re-renders.
If your list elements don’t have a unique identifier, you may be tempted to use the array index of each element as the key
such as:
const Todos = ({ todos }) => {
return (
<ul>
{todos.map((todo, index) =>
<li key={`${index}`}>{todo.value}</li>
)}
</ul>
);
};
This can be fine if your todos
list doesn’t change. But it will become problematic if:
The list is re-ordered
An element of the list (that is not the last element) is removed. Worse, if this element that is removed is the first one in the list since the index will be changed for all the other elements.
In these 2 cases, the indexes of the elements in the array will be shifted and different, yet the element themselves haven’t changed. This will force React to mutate all the elements in the list, even the elements that didn’t change. It can negatively impact performance and may cause issues with component state.
To solve this issue, you could add a unique identifier to your elements, or create a unique hash that you can use as a key based on the content of the element. For example:
{todos.map((todo) =>
<li key={todo.id}>{todo.value}</li>
)}
Tip #5: Make sure to cleanup effects when your component unmounts
If you’re using useEffect()
hooks in your component to handle side effects such as fetching data, setting intervals, setting timeouts, or adding event listeners, it is important to cleanup these effects when the component unmounts to avoid memory leaks, and in some cases to avoid having event listener registered many times.
In the useEffect()
callback, it’s possible to return a function, and this function will be executed after your effect has run:
useEffect(() => {
// do something async
// ...
return () => {
// cleanup function
// This will be executed after the effect has run.
// Since this effect does not have any dependency
// this will be executed when the component is unmounted.
};
}, []);
With this, you can avoid updating the state of a component when your component is unmounted. For example if you do this:
const [message, setMessage] = useState('');
useEffect(() => {
const helloTimeout = setTimeout(() => {
setMessage('Hello, after 1 second');
}, 1000);
return () => {
clearTimeout(helloTimeout);
};
}, []);
Here, it’s important to cleanup this effect with clearTimeout(handleTimeout);
so that if your component gets unmounted for some reason before the timeout finished, you will avoid updating the state of the component. Updating the state of an unmounted component can lead to memory leaks. In development, you will see a warning in the console in the development tools of your browser, which helps to identify those kind of issues.
That’s it for our React tips for the week! Do you have any unique tips you want to add? Let us know below.