The React Hooks feature was proposed in October 2018 and released ~4 months later in February 2019. Since then, people have been rapidly learning and adopting hooks in their production codebases because hooks drastically simplify the management of state and side effects in applications.
It definitely took its rightful place as "the new hotness." But as hot as it is, React Hooks require a bit of a change in the way you think about React Component Lifecycles, State, and Side Effects and it can be easy to fall into problematic scenarios if you're not thinking about React Hooks properly. So let's look a bit at what pitfalls you could come across and how you can change your thinking so you avoid them.
Pitfall 1: Starting without a good foundation
The React Hooks documentation is brilliant and I strongly recommend you give it a read through, especially the FAQ which has a LOT of helpful information. So give yourself a good hour or two and just read the docs without touching your keyboard. It'll give you a great overview of hooks conceptually and save you a lot of time.
Also, don't skip the React Conf talks by Sophie, Dan, and Ryan that introduce hooks.
To avoid the first pitfall: Read the docs and the FAQ 📚
Pitfall 2: Not using (or ignoring) the ESLint plugin
Around the time Hooks was released, the eslint-plugin-react-hooks package was built and released. It has two rules: "rules of hooks" and "exhaustive deps." The default recommended configuration of these rules is to set "rules of hooks" to an error, and the "exhaustive deps" to a warning.
I strongly advise that you install, use, and follow these rules. It will not only catch real bugs that you can easily miss, but it will also teach you things about your code and hooks in the process (not to mention the awesome auto-fix feature).
Imagine you have a component that shows dog information when a dog is clicked:
function DogInfo({ dogId }) {
const [dog, setDog] = useState(null);
useEffect(() => {
getDog(dogId).then((d) => setDog(d));
}, []); // 😱
return {/* render the dog info here */};
}
Notice that the effect will not re-run on dogId change, which could cause bugs. You can fix this by adding the dependency:
function DogInfo({ dogId }) {
const [dog, setDog] = useState(null);
useEffect(() => {
getDog(dogId).then((d) => setDog(d));
}, [dogId]); // ✅
return {/* render the dog info here */};
}
To avoid the second pitfall: Install, use, and follow the ESLint plugin 👨🏫
Pitfall 3: Thinking in Lifecycles
With React Hooks, you don’t need to think about lifecycle methods like componentDidMount or componentDidUpdate. Instead, you synchronize side effects with state changes using useEffect:
function HookComponent() {
React.useEffect(() => {
// Side effect code here
return function cleanup() {
// Clean up previous side-effect
};
}, [dependencies]);
return /* some beautiful react elements */;
}
To avoid this pitfall: Don't think about Lifecycles, think about synchronizing side effects to state 🔄
Pitfall 4: Overthinking performance
Some developers worry about performance when they see functions being redefined inside components. However, in most cases, defining functions inside a component is not a performance issue because JavaScript engines are fast at defining functions.
If you're facing performance issues due to unnecessary re-renders, first investigate what's causing the slow renders. In most cases, React handles rendering performance well, but you can optimize with React.memo, useMemo, and useCallback if necessary.
To avoid this pitfall: Know that React is fast by default and do some digging before applying performance optimizations prematurely 🏎💨
Pitfall 5: Overthinking the testing of hooks
Many developers worry about rewriting tests when they refactor to hooks. However, if your tests are written to interact with what’s rendered, you won't need to rewrite them. Focus on testing the behavior of the component rather than its implementation details.
test('can open accordion items to see the contents', () => {
const items = [
{ title: 'Favorite Hats', contents: 'Fedoras are classy' },
{ title: 'Favorite Footware', contents: 'Flipflops are the best' },
];
render( );
expect(screen.getByText(hats.contents)).toBeInTheDocument();
expect(screen.queryByText(footware.contents)).toBeNull();
userEvent.click(screen.getByText(footware.title));
expect(screen.getByText(footware.contents)).toBeInTheDocument();
expect(screen.queryByText(hats.contents)).toBeNull();
});
To avoid this pitfall: Avoid testing implementation details 🔬