Technical Failure #1: React hooks dependencies

| 2 min read

React introduces hooks in 16.8 and everything changed. Hooks provided an opportunity to make functional components with state and lifecycles. The new approach required redefined mental models of how to write code in React. Not fully switching to a new approach lead to issues.


The work of a technical leader is full of decisions. You need to make them at the start and they can have an impact on the project even on the finish line. This series of article is for people who want to learn from my mistakes:

  1. React hooks dependencies

Situation

Let’s imagine we have a root component with a state that changes frequently and a component connected to the backend.

This code update Root component state every 3s. Every time counter is incremented ArticlesWithMutipleRerenders component is re-rendered. This leads to the calling getArticles method every time the counter is changing.

Why is this happen?

React uses reference equality when comparing dependencies to figure out it should run useEffect callback or not. In this case the following assign:

const db = DBConnection();

every render db changes its reference, even if the value of this variable is the same as before.

That’s why useEffect callback runs every time component is rendered:

useEffect(() => {
console.count("get articles");
db.getArticles().then(setArticles);
}, [db]);

Bad decision

My decision seemed the easiest one and the simplest. When I was using db with useEffect or any hook, I just omitted this dependency. Code looked like this and everything was fine at the beginning:

const db = DBConnection();
useEffect(() => {
console.count("get articles");
db.getArticles().then(setArticles);
}, []);

The worst decision

One thing still bothered me. I got a warning from eslint that db dependency should be included in the dependency array.

Eslint rule warning

After the bad decision, there was an even worse one. I suppressed all of that in-place with eslint-disable-next-line. This led me to make it in every file multiple times and code loose much readability.

The right way

Everything we need to do is memoize db and add it to the dependency array.

Why is this the best way?

  • DBConnection can make multiple instances of the database connection. We want to keep as few connections as possible. That’s why we are creating one instance of the database connection.
  • Passing db instance ensure that when instance changes we fetch articles again.
const db = useMemo(() => DBConnection(), []);
useEffect(() => {
console.count("get articles");
db.getArticles().then(setArticles);
}, [db]);

Conclusion

The right code you can check on codesandbox. Making mistakes is the way we learn. Development is making decisions and draws conclusions based on results.

Let me know in the comments below if you have any questions. You can find me on Twitter.