window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster already exists') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() A Deeper Look at the Most Popular React Hooks - The Codest
The Codest
  • About us
  • Services
    • Software Development
      • Frontend Development
      • Backend Development
    • Staff Augmentation
      • Frontend Developers
      • Backend Developers
      • Data Engineers
      • Cloud Engineers
      • QA Engineers
      • Other
    • It Advisory
      • Audit & Consulting
  • Industries
    • Fintech & Banking
    • E-commerce
    • Adtech
    • Healthtech
    • Manufacturing
    • Logistics
    • Automotive
    • IOT
  • Value for
    • CEO
    • CTO
    • Delivery Manager
  • Our team
  • Case Studies
  • Know How
    • Blog
    • Meetups
    • Webinars
    • Resources
Careers Get in touch
  • About us
  • Services
    • Software Development
      • Frontend Development
      • Backend Development
    • Staff Augmentation
      • Frontend Developers
      • Backend Developers
      • Data Engineers
      • Cloud Engineers
      • QA Engineers
      • Other
    • It Advisory
      • Audit & Consulting
  • Value for
    • CEO
    • CTO
    • Delivery Manager
  • Our team
  • Case Studies
  • Know How
    • Blog
    • Meetups
    • Webinars
    • Resources
Careers Get in touch
Back arrow GO BACK
2021-12-07
Software Development

A Deeper Look at the Most Popular React Hooks

The Codest

Pawel Rybczynski

Software Engineer

In the course of many interviews, I noticed that even experienced programmers have a problem with distinguishing Hooks, not to mention their more advanced capabilities. So, I will try to explain in this article how Hooks should be used.

The most important things you need to remember about Hooks:

  • they can be used only in function components – class components have own lifecycle implementation;
  • they always start with use;
  • you can use as may Hooks as you want, but you need to remember that using them has an impact on the overall performance;
  • they must be executed in the same order on each render…but why? Let’s take a look at an example:
import { useState,useEffect } from "react";

export default function FunctionComponent() {
const [value, setValue] = useState(1);
const [doubleValue, setDoubleValue] = useState(1);
if (value > 3) {
  useEffect(() => setDoubleValue(value * 2),[value]);
}

return (
  <>
    <p>{`Single ${value} Double ${doubleValue}`}</p>
    <button onClick={() => setValue(value + 1)}>Check</button>
  </>
);
}
```

At first, you will receive a warning from eslint:

srcFunctionComponent.js
   Line 11:5:  React Hook "useEffect" is called conditionally. <strong>React Hooks</strong> must be called in the exact same order in every component render  .eslintreact-hooks/rules-of-hooks

As you can see, it’s only an eslint warning so you can disable it by adding a command from below at the top of the FunctionComponent

/* eslint-disable react-hooks/rules-of-hooks */ 

and it will work but only till we fulfill the condition that runs our Hook. The very next thing which we will see is this error.

Uncaught Error: Rendered more hooks than during the previous render.
     React 5
     FunctionComponent FunctionComponent.js:11
     React 12
     unstable_runWithPriority scheduler.development.js:468
     React 17
     js index.js:7
     js main.chunk.js:905
     Webpack 7
 react-dom.development.js:15162

Why this happens? React relies on the order in which the Hooks are called as React wouldn’t know what to return for the useEffect because there was no such Hook in the line to check.

Remember, eslint is a powerful tool, it helps us catch a lot of potential bugs and errors. Disabling its warnings is a dangerous thing, always check if ignoring the warning can cause an app crash.

useState

You probably know how it looks 😉

const [value, setValue] = useState(0);

So, you have 4 elements: state (reactive value), update function (setter), actual hook (function) and optional initial value. Why it returns an array? Because we can restructure it as we like.

Now I want to focus on the last element – the initial value. There are two ways to pass the initial state:

  1. By hardcoded value or smth – which will be called on each render
const [value, setValue] = useState(0);
  1. By a function version. It`s really helpful if we want to run the initial state only once, on the very first render. Maybe you need to make a lot of complex calculations to receive the initial state? It will nicely decrease the resource cost, yay!
const [value, setValue] = useState(() => {
   console.log("INIT");
   return 0;
 });

How to check that the first way is really called on each render? Create a function and pass it as an initial state:

const checkInit = () => {
console.log("INIT");
return 0;
};

const [value, setValue] = useState(checkInit());
```

And now pass it using the second way:

const checkInit = () => {
console.log("INIT");
return 0;
};

const [value, setValue] = useState(() => checkInit());
```

Cool, right?

noice.png

Another thing which can create bugs in the app flow: you probably know how to update a state, right?

setValue(1);

Right… but what if I want to update the state based on a previous state?

setValue(value + 1);

Yes… But no… What if you’ll try to call the setter function twice, one after another? The recommended way to update a state based on the previous state is to use a function. It guarantees that you are referring to the previous state

setValue((prevState) => prevState + 1);
// with objects:
setUser((prevState) => ({ ...prevState, lastName: "Brzeczyszczykiewicz" }));

useEffect

This Hook take 2 arguments (the second one is optional) and we use it to handle side effects. And depending on what we pass as a second argument, the Hook will be called differently:

  1. without second argument – each render
useEffect(() => {
   doSomething();
 });
  1. empty array – only on first render
useEffect(() => {
   doSomething();
 }, []);
  1. array with dependencies – every time the value in the dependency array changes
useEffect(() => {
   doSomething(value);
 }, [value]);

Cleanup

With useEffect, we can use something what is called cleanup. What is it for? It is very useful, but I think it’s best for cleaning event listeners. Let’s say you want to create an event listener which depends on some state. You don`t want to add a new event listener on each state change, because after a few renders there will be so many listeners that it will affect the app’s performance. A great way to avoid such things is to use the cleanup feature. How to do it? Just add a return function to the useEffect.

useEffect(() => {
console.log("side effect 1", count);
return () => {
console.log("DESTROYED 1");
};
});

useEffect(() => {
console.log("side effect 2", count);
return () => {
console.log("DESTROYED 2");
};
}, []);

useEffect(() => {
console.log("side effect 3", count);
return () => {
console.log("DESTROYED 3");
};
}, [count]);
```

Because it`s inside the useEffect Hook, the return is called depending on the dependency array – on each render, only on the first render, or when the value in the dependency array changes. But when the component is unmounted, cleaning will be called on the second argument no matter what. The return code is called before the actual code from Hook. It’s very logical – at first clean the old one, then create a new one. Right?

useEffect(() => {
   // addEventListener
   console.log("Add");
   return () => {
     // removeEventListener
     console.log("Remove");
   };
 }, [value]);

So, first, you will receive a remove message, then Add.

There is one thing to be look out for when using useEffect and asynchronous code inside it. Take a look at the code below:

useEffect(() => {
   fetch("https://picsum.photos/5000/5000").then(() => {
     setValue((prevState) => prevState + 1);
   });
 }, []);

At first, it looks ok. You are fetching some data, and when the data comes, update the state. And here’s the trap:

Sometimes you will receive such a warning:
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

The reason is that the component can be unmounted in meantime, but the app will still try to update the state of that component after the promise was fulfilled. How to deal with it? You need to check whether the component exists.

useEffect(() => {
let mounted = true;
fetch("https://picsum.photos/5000/5000").then(() => {
if (mounted) {
setValue((prevState) => prevState + 1);
}
});

return () => {
mounted = false;
};
}, []);
```

Note: There is a very similar Hook => useLayoutEffect() – the callback runs after rendering the component but before the dom is visually updated. It’s useful when working with getBoundingClientRect(), but you should use useEffect by default. Why? Because it can block visual updates – when you have a complex code inside your effect Hook.

useContext

What is it for? Sharing data without passing props. Consists of the following elements:

  1. Created context – data
  2. Context Provider – provide context to all children
  3. Passed value – data you want to share
  4. Hook – to read shared data
const user = {
name: "Adam",
lastName: "Kowalski",
};

export const UserContext = createContext(user);

;
```

In child, you need to import the context and call the useContext Hook and pass that context as an argument.

import { UserContext } from "./App";

const { name } = useContext(UserContext);

return <h1>Hi {name}<>
```

Voilà. Looks cool. Mostly for passing global data like themes, etc. Not recommended for using in tasks with very dynamic changes.

Of course, we can create a custom context provider and a custom Hook to reduce the boilerplate instead… but I will deal with custom Hooks in the next article.

useReducer

It allows us to manage the state and re-render when the state changes – like useState. It`s similar to the redux reducer. This one is better than useState when state logic is more complicated.

const [state, dispatch] = useReducer(reducer, initialArg); 
  • Returns actual state with a dispatch method.
  • Differently than in redux, the initial value is specified when the Hook is called.

There is also a third argument which can be passed to the useReducer – the init function.

const [state, dispatch] = useReducer(reducer, initialArg, init);

What is it for? It can be used when we want to reset the state to its initial value. Below you can find a nice example:

// Parent

<ChildComponent initialNumber={1} />
// Child
function init(initialNumber) {
return { number: initialNumber };
}

function reducer(state, action) {
switch (action.type) {
case "change":
return { number: Math.random() };
case "reset":
return init(action.payload);
default:
throw new Error();
}
}

export default function ChildComponent({ getFactorial }) {
const [state, dispatch] = useReducer(reducer, initialNumber, init);

return (
<>
   <h2>Number: {state.number}</h2>
      <button
        onClick={() => dispatch({ type: "reset", payload: initialNumber })}
      >
        Reset
      </button>
      <button onClick={() => dispatch({ type: "change" })}>Draw</button>
    </>
  );
}

Number: {state.number}

ReducerInit.png

useCallback

When to use it? When we want to achieve referential equality (thereby reducing the number of created functions). This Hook returns the function, unlike useMemo which returns the value.

Example: Create a function in the parent component and then pass it via props

// Parent
 const getSquaredValue = () => count * count;
 ...
 return (
   <ChildComponent getSquaredValue={getSquaredValue}>
 )

Then check in child component how many times the effect Hook will be called after adding that function to the dependency array:

// Child
 useEffect(() => {
   console.log("getSquaredValue", getSquaredValue());
 }, [getSquaredValue]);

It will log to the console on every render! Even if the values inside the getSquaredValue() function didn’t change. But we can avoid this by wrapping that function in useCallback

const getSquaredValue = useCallback(() => count * count, [count])

We also can pass some parameters to this function:

const getSquaredValue = useCallback(
   (multiplier) => count * count * multiplier,
   [count]
 );

useMemo

const memoizedValue = useMemo(() => {
   return doSomething(value);
 }, [value]);
  • It is not neutral when looking at the costs of resources – useMemo must be called on every render, is saves the value in memory and compares (memory overhead),
  • uses Memoization – the optimization technique, specific form of caching.

You should use it in 2 scenarios only:

  1. If you want to prevent calling a complex code on each render;
  2. If you want to achieve referential equality.

Let`s look a little bit closer at the second case. We want to use useEffect with an object as a dependency. Because objects are compared by their reference, useEffect will be called on each render. To avoid such things, we can combine useEffect with useMemo to memoize such objects and then pass that memoized objects to the dependency array. Short example:

First try to do it without useMemo:

const hobbit = { name: "Bilbo" };

useEffect(() => {
console.log("Hello ", hobbit.name);
}, [hobbit]);
```

Also, you will receive a warning:

The 'hobbit' object makes the dependencies of the useEffect Hook (line 49) change on every render. Move it inside the useEffect callback. Alternatively, wrap the initialization of 'hobbit' in its own useMemo () Hook.eslintreact-hooks/exhaustive-deps

Then try with useMemo:

const hobbit = useMemo(() => {
return { name: "Bilbo" };
}, []);

useEffect(() => {
console.log("Hello ", hobbit.name);
}, [hobbit]);
```

useRef

The most important thing: useRef does not trigger re-rendering (like useState) because it`s not connected to the render cycle – it keeps the same reference between renders.

const ref = useRef(0);

To call the saved value, you need to use a current property (ref is an object) – ref.current

The second case for which we can use that Hook is to reference elements inside HTML. Each element has a ref attribute. So, we can handle focus, events etc.

The third case is that we can use refs to handle uncontrolled components. You can read more about them in react docs,
but in short, it looks like this:

export default function UncontrolledForm() {
  const input = useRef();

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(input.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" ref={input} />
      <button type="submit">Submit</button>
    </form>
  );
}

As you can see, there is no event handler, it just remembers the typed value. It’s great for handling basic forms when you just want to read saved values when you need them (like when submitting).

Bonus: It`s great when you need to remember previous state values. You can use for it the useEffect Hook, just pass the state to the ref.

const [value, setValue] = useState("");

let prevValue = useRef("");

useEffect(() => {
  prevValue.current = value;
}, [value]);

<input value={value} onChange={(e) => setValue(e.target.value)}></input>;

As you can see, Hooks are not that obvious. We can combine them to solve many problems. You will surely benefit greatly from studying this topic.

And there are also custom hooks…

In conclusion, React hooks have revolutionized the way React developers approach building web applications . By providing a more intuitive and efficient way to manage state and lifecycle in functional components, hooks have become an integral part of React development .

Whether you’re a seasoned developer or just starting with React, understanding the most popular hooks and their use cases is crucial. With hooks like useState, useEffect, useContext, and more, React components can be built with cleaner and more reusable code. Moreover, the ability to create custom hooks allows developers to encapsulate and share logic across multiple components, promoting code reusability and modularity. As React continues to evolve and introduce new features, hooks will undoubtedly play a central role in leveraging the full potential of the framework.

So, whether you’re working on a small function app or a large-scale web application, embracing React hooks will enhance your development workflow and unlock a plethora of possibilities for creating robust and feature-rich React applications .

End of Part 1

Read more:

JavaScript Is Totally Dead. Some Dude on the Internet

Deploy GraphQL/MongoDB API Using Netlify Functions

How to Kill a Project with Bad Coding Practises

Related articles

Software Development

Build Future-Proof Web Apps: Insights from The Codest’s Expert Team

Discover how The Codest excels in creating scalable, interactive web applications with cutting-edge technologies, delivering seamless user experiences across all platforms. Learn how our expertise drives digital transformation and business...

THECODEST
Software Development

Top 10 Latvia-Based Software Development Companies

Learn about Latvia's top software development companies and their innovative solutions in our latest article. Discover how these tech leaders can help elevate your business.

thecodest
Enterprise & Scaleups Solutions

Java Software Development Essentials: A Guide to Outsourcing Successfully

Explore this essential guide on successfully outsourcing Java software development to enhance efficiency, access expertise, and drive project success with The Codest.

thecodest
Software Development

The Ultimate Guide to Outsourcing in Poland

The surge in outsourcing in Poland is driven by economic, educational, and technological advancements, fostering IT growth and a business-friendly climate.

TheCodest
Enterprise & Scaleups Solutions

The Complete Guide to IT Audit Tools and Techniques

IT audits ensure secure, efficient, and compliant systems. Learn more about their importance by reading the full article.

The Codest
Jakub Jakubowicz CTO & Co-Founder

Subscribe to our knowledge base and stay up to date on the expertise from the IT sector.

    About us

    The Codest – International software development company with tech hubs in Poland.

    United Kingdom - Headquarters

    • Office 303B, 182-184 High Street North E6 2JA
      London, England

    Poland - Local Tech Hubs

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Warsaw, Poland

      The Codest

    • Home
    • About us
    • Services
    • Case Studies
    • Know How
    • Careers
    • Dictionary

      Services

    • It Advisory
    • Software Development
    • Backend Development
    • Frontend Development
    • Staff Augmentation
    • Backend Developers
    • Cloud Engineers
    • Data Engineers
    • Other
    • QA Engineers

      Resources

    • Facts and Myths about Cooperating with External Software Development Partner
    • From the USA to Europe: Why do American startups decide to relocate to Europe
    • Tech Offshore Development Hubs Comparison: Tech Offshore Europe (Poland), ASEAN (Philippines), Eurasia (Turkey)
    • What are the top CTOs and CIOs Challenges?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Website terms of use

    Copyright © 2025 by The Codest. All rights reserved.

    en_USEnglish
    de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek en_USEnglish