In this article, my main goal is to share with you all some tips to write better React Functional Components! And, of cooourse, I invite you to share with me in the comments your coding practices when developing components! 😄
When do I need to create a new component?
Components usually emerge from other components during development and that's super normal! While prototyping a component, it is important to realize if it is concise or if it takes on too many responsibilities that can be broken into other components, which can also be reusable, thus making the code cleaner and more readable. The more concise this code is, the better for your tests, maintenance and performance!
In the example below, I could clearly create the header inside of the App, but it is better and more abstract to create a component whose responsibility is to represent the header of the app, and we can test it independently! 😉
App:
class App extends Component {
render() {
return (
<div className="App">
<Header />
</div>
);
}
}
Header:
const Header = () => (
<div className="header">
<img src={logoUrl} alt="CompanyName" />
<h1>CompanyName</h1>
</div>
)
Memoize data with useMemo
Memoization is important to avoid unnecessary recalculation of the result again and again for the same input - especially when the calculation is expensive, which may mean that the data set on which computation needs to be performed is laaarge! 😢😢😢
So here comes useMemo
hook, which applies memoization to any value type and may give you a performance optimization! By passing a function that returns the value, that function is only called when the value needs to be updated (which typically will only happen once each time an element in the dependencies array changes between renders). It is fundamental to deal with large data and complicated data, keeping its memoization. For example, when sorting, filtering, applying some logic or even processing this data on the client-side, it should not be done every time a component re-renders and the data remains the same!
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Despite the benefits of useMemo
, it is not always the best solution. This fabulous article brings us a benchmark of the performance gains and performance trade-offs of useMemo
, which is important to be taken into account. For instance, when the data is not that large, maybe the best approach is to avoid using this hook 😰
Memoize callback functions with useCallback
We usually say that useCallback
works as useMemo
, but the difference is that it’s used to memoize function declarations, preventing its re-creation.
To illustrate it, let's take this example: usually, if you have a function B inside of a functional component A, the inner function B will be re-created whenever component A re-builds.
const A = props => {
const B = () => {
doSomething();
};
useEffect(() => B()}, [B]);
};
In this case, useEffect
will be called every time that function B changes. In other words, it means that it will be called for every render cycle of component A! And... do you know what happens if the function B causes an update to the component A?! It would be in a loop... forever 🤡🤡🤡
So here we have an example of good usage of useCallback
. This hook will ensure that the function is only re-created if its dependencies changed:
const B = useCallback(() => {
doSomething();
}, [dependencies]);
Does this function REALLY need to be in the component?
Sometimes the simplest way to avoid unnecessary problems is to analyze your component and understand which inner functions can be outside this component - in other words, functions that do not depend on anything from the component.
Perhaps the function just receives a string and formats it. Or just does some operations based on a numeric entry. Or even the function generates a URL based on some data provided...
Why not take it out of the component?
It makes this function easier to test, reuse and even makes the component more readable! 🥰