DEV Community

Cover image for useCallback, useMemo, useRef, and useReducer hook
Jehad Hossain
Jehad Hossain

Posted on • Updated on

useCallback, useMemo, useRef, and useReducer hook

In this article, I’m going to discuss the use of useCallback, useMemo, useRef, and useReducer hook.

useCallback hook: It memorizes a callback function. So new instances of that function are not created. And it will only forget it when the dependency value is changed.

import React, { useCallback, useState } from "react";
import "./App.css";
import Title from "./components/Title/Title";

function App() {
 const [increase1, setIncrease1] = useState(0);
 const [increase5, setIncrease5] = useState(0);

 const handleIncrease1 = useCallback(() => {
   console.log("inside 1");
   setIncrease1(increase1 + 1);
 }, [increase1]);

 const handleIncrease5 = useCallback(() => {
   console.log("inside 5");
   setIncrease5(increase5 + 5);
 }, [increase5]);

 return (
   <div className="App">
     <Title />
     {/* increase value by 1 */}
     <h4>Increase value by 1</h4>
     <p>{increase1}</p>
     <button onClick={handleIncrease1}> Increase by 1</button>

     {/* increase value by 5 */}
     <h4>Increase value by 5</h4>
     <p>{increase5}</p>
     <button onClick={handleIncrease5}> Increase by 5</button>
   </div>
 );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

UseMemo hooks: It’s a little different from useCallback hook. useCallback memorizes the whole function but it memorizes only the return value of a function. And it forgets that when the dependency value is changed.

import React, { useCallback, useMemo, useState } from "react";
import "./App.css";
import Title from "./components/Title/Title";

function App() {
 const [increase1, setIncrease1] = useState(0);
 const [increase5, setIncrease5] = useState(0);

 const handleIncrease1 = useCallback(() => {
   console.log("inside 1");
   setIncrease1(increase1 + 1);
 }, [increase1]);

 const handleIncrease5 = useCallback(() => {
   console.log("inside 5");
   setIncrease5(increase5 + 5);
 }, [increase5]);

 const isEvenOrOdd = useMemo(() => {
   let i = 0;
   while (i < 1000000000) i++;
   return increase1 % 2 === 0;
 }, [increase1]);

 return (
   <div className="App">
     <Title />
     {/* increase value by 1 */}
     <h4>Increase value by 1</h4>
     <p>{increase1}</p>
     <p>{isEvenOrOdd ? "Even" : "Odd"}</p>
     <button onClick={handleIncrease1}> Increase by 1</button>

     {/* increase value by 5 */}
     <h4>Increase value by 5</h4>
     <p>{increase5}</p>
     <button onClick={handleIncrease5}> Increase by 5</button>
   </div>
 );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

useRef: If we want to get a element or its value then we use document.getElementById or document.getElementsByClassName etc. You can use them on react but it is not a good practice. To solve this react give us useRef hook. You just have to say which node element you want to reference. Then it will return the referenced node in the referenced variable.current

import React, { useEffect, useRef } from "react";

const Form = () => {
  const inputRef = useRef();

  useEffect(() => {
    console.log(inputRef.current.value);
  }, []);
  return (
    <div>
      <input ref={inputRef} />
    </div>
  );
};

export default Form;
Enter fullscreen mode Exit fullscreen mode

If you have to use ref to a child component then you have to use forward ref to pass ref from parent to child component

Parent component where the ref is located

import React, { useEffect, useRef } from "react";
import "./App.css";
import Input from "./components/Input";

function App() {
 const inputRef = useRef();

 useEffect(() => {
   inputRef.current.focus();
 });
 return (
   <div className="App">
     <Input placeholder="Enter your name" ref={inputRef} type="text" />
   </div>
 );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Child component where I want to use the ref

import React from "react";

const Input = ({ type, placeholder }, ref) => {
 return <input ref={ref} type={type} placeholder={placeholder} />;
};

const forwardedInput = React.forwardRef(Input);

export default forwardedInput;
Enter fullscreen mode Exit fullscreen mode

Here’s a little trick of using useRef. When you want to use a function which is scoped inside the useEffect hook.

import React, { useEffect, useRef, useState } from "react";

const Clock = () => {
 const [date, setDate] = useState(new Date());
 const intervalRef = useRef();

 const tick = () => {
   setDate(new Date());
 };

 useEffect(() => {
   intervalRef.current = setInterval(tick, 1000);

   // do the cleanup stop the timer
   return () => {
     clearInterval(intervalRef.current);
   };
 }, []);
 return (
   <div>
     <p>Time: {date.toLocaleTimeString()}</p>
     <p>
       <button onClick={() => clearInterval(intervalRef.current)}>Stop</button>
     </p>
   </div>
 );
};

export default Clock;
Enter fullscreen mode Exit fullscreen mode

useReducer: useReducer is like the Array.prototype.reduce method in vanilla JS. The difference is that reduce method takes a reducer function and initialValue but useReducer takes the reducer function and initialState as the second parameter. Reduce method returns a single value but useReducer returns a tuple [newState, dispatch]

import React, { useReducer } from "react";
import "./App.css";

const initialState = 0;
const reducer = (state, action) => {
 switch (action) {
   case "increment":
     return state + 1;
   case "decrement":
     return state - 1;
   default:
     return state;
 }
};
function App() {
 const [count, dispatch] = useReducer(reducer, initialState);
 return (
   <div className="App">
     <p>{count}</p>
     <button onClick={() => dispatch("increment")}>Increase</button>
     <button onClick={() => dispatch("decrement")}>Decrease</button>
   </div>
 );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

You can use useReducer in a complex situation where you have different as for example counters and they have a different functions to update their value.

import React, { useReducer } from "react";

const initialState = {
 counter: 0,
 counter2: 0,
};
const reducer = (state, action) => {
 /* you have to merge object immutably to do that use the spread operator */
 switch (action.type) {
   case "increment":
     return { ...state, counter: state.counter + action.value };
   case "decrement":
     return { ...state, counter: state.counter - action.value };
   case "increment2":
     return { ...state, counter2: state.counter2 + action.value };
   case "decrement2":
     return { ...state, counter2: state.counter2 - action.value };
   default:
     return state;
 }
};

/* you have to merge state immutably */
function Counter() {
 const [count, dispatch] = useReducer(reducer, initialState);
 return (
   <React.Fragment>
     {/* counter 1 */}
     <div>
       <p>Counter1: {count.counter}</p>
       <button
         onClick={() =>
           dispatch({
             type: "increment",
             value: 1,
           })
         }
       >
         Increment by 1
       </button>

       <button
         onClick={() =>
           dispatch({
             type: "decrement",
             value: 1,
           })
         }
       >
         Decrement by 1
       </button>
     </div>

     {/* counter 2 */}
     <div>
       <p>Counter2: {count.counter2}</p>
       <button
         onClick={() =>
           dispatch({
             type: "increment2",
             value: 1,
           })
         }
       >
         Increment2 by 1
       </button>

       <button
         onClick={() =>
           dispatch({
             type: "decrement2",
             value: 1,
           })
         }
       >
         Decrement2 by 1
       </button>
     </div>
   </React.Fragment>
 );
}

export default Counter;
Enter fullscreen mode Exit fullscreen mode

Top comments (0)