Hooks in React

Pooja Mehta
7 min readAug 22, 2020

--

Photo by Efe Kurnaz (Unsplash)

Hooks are a new addition to React v16.8. They are functions that let you use states and lifecycle features without creating classes in React. They do not work with classes — they let you create components in React without classes. They are completely backwards compatible, so you need not re-write your existing class components.
Official React Documentation

What are the benefits of using Hooks?

  • Shallow learning curve
  • Easier to understand and read the code
  • Easier to unit test
  • Makes complex components look cleaner
  • Organise logic inside component into re-usable isolated functions
  • Avoids duplication of code between components
  • Performance booster
  • Less lines of code compared to classes
  • Encourages use of functional components

What are the rules to use Hooks?

  • Hooks only work with functional components.
  • Hooks should be invoked at the beginning of the component
  • Hooks should not be invoked inside nested functions, loops or conditions
  • All hooks must start with keyword “use” to easily identify from other functions

What are the different type of Hooks?

React is shipped with various built-in hooks which are used for different purposes. We will learn about each type below —

1. useState

  • It is a hook which is used to declare state variables in functional components.
  • It returns two things — current value and a method to update the current state value.
  • It can be used in single functional component as many times as required.
  • It takes one argument which is used to set its initial value.
import React, { useState } from "react";function UseStateDemo() {
// count is a state variable with initial value 0 and setCount is method to update the value of count state variable
const [count, setCount] = useState(0);
const handleBtnClick = () => {
setCount(count + 1);
};
return (
<div>
<button onClick={() => handleBtnClick()}>Counter is {count}</button>
</div>
);
}
export default UseStateDemo;

2. useEffect

  • It is a hook which is used to perform operations when side effects occur in functional components.
  • It can be used in single functional component as many times as required.
  • It can be defined when we want to establish socket connections or manage subscriptions to events or perform operations when specific variable value changes.
  • It takes two arguments — first being the function to be invoked and second being the dependencies which needs to be observed. If dependencies are not defined, it will be executed every time after component is re-rendered.
  • It can be invoked during either/all of the component mounting, component updating or component unmounting phases.
  • If we are defining any event listeners or setting timers in our component, it must be cleaned up at the time of component unmounting; it can be done by using useEffect hook.
import React, { useState, useEffect } from "react";function UseEffectDemo() {
const [count, setCount] = useState(0);
const handleBtnClick = () => {
setCount(count + 1);
};
useEffect(() => {
// this will be triggered everytime component re-renders
console.log("Count value is changed");
return () => {
// this will be triggered everytime component unmounts
console.log("Component is going to be unmounted");
};
});
return (
<div>
<button onClick={() => handleBtnClick()}>Counter is {count}</button>
</div>
);
}
export default UseEffectDemo;

3. useContext

  • It is a hook that is used to use the values which are obtained from Context Providers of other components. This prevents prop drilling, i.e. manually passing props from topmost component to the inner most component and still be able to access them.
  • It takes context object as argument and returns the current context value.
  • The functional component which has “useContext” hook defined at the top will always be re-rendered if any value in the context provider changes.
import React, { useState, useContext } from "react";import myContext from "./MyApp";function UseContextDemo() {
// context will contain all values defined in the myContext.Provider of MyApp component
const context = useContext(myContext);
const [count, setCount] = useState(0);const handleBtnClick = () => {
setCount(count + 1);
};
return (
<div>
<button onClick={() => handleBtnClick()}>Counter is {count}</button>
</div>
);
}
export default UseContextDemo;

4. useCallback

  • It is a hook that can be used when the component re-renders a lot and we want to prevent the methods from being re-created every time without any changes in them.
  • It takes two argument — one being the method and second being the dependencies which must be changed for method to be re-created again. If we provide empty array as dependencies, methods are created only once, but if we do not pass anything as dependency, it will be re-created every time when component is re-rendered.
  • It returns the cached function if dependencies are not changed.
import React, { useState, useCallback } from "react";function UseCallbackDemo() {
const [count, setCount] = useState(0);
// method is created only when count value changes
const increment = useCallback(() => {
setCount(count + 1);
}, [count]);
// method is created only when count value changes
const decrement = useCallback(() => {
setCount(count - 1);
}, [count]);
return (
<div>
<button onClick={() => increment()}>Increment</button>
<button onClick={() => decrement()}>Decrement</button>
<div>Counter is {count}</div>
</div>
);
}
export default UseCallbackDemo;

5. useMemo

  • It is a hook that is used when a method does some heavy computation and we want to avoid it being done on every component re-render without any of its related bits being changed.
  • It takes two arguments — one is the method and other is the dependencies which is required to be changed for the computation to be done again. If we pass an empty array as dependencies it will be computed only once when component mounts. If we do not pass any dependencies, then it will be computed on every re-render.
  • It returns a memoized value everytime if the dependencies are not changed.
  • It is a hook which is executed during the time of rendering so it is a best practice to use it only when necessary and as a way to optimize performance.
import React, { useState, useMemo } from "react";function UseMemoDemo() {
const [array, setArray] = useState([1, 2, 3, 4, 5]);
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
setArray([...array, count]);
};
const decrement = () => {
setCount(count - 1);
};
// this is recomputated only when array mutates
const heavyComputation = useMemo(() => {
return array.reduce((a, b) => a + b, 0);
}, [array]);
return (
<div>
<button onClick={() => increment()}>Increment</button>
<button onClick={() => decrement()}>Decrement</button>
<div>Counter is {count}</div>
<div>Sum is {heavyComputation}</div>
</div>
);
}
export default UseMemoDemo;

6. useRef

  • It is a hook that is used to store references to DOM elements throughout the component lifetime and also in between re-renders.
  • It returns mutable ref object which can be initialised with an initial value given as an argument.
  • We can access the entire DOM element by accessing the “counterRef.current” as given in the example.
  • It does not trigger component re-render on mutating the .current property.
import React, { useState, useRef } from "react";function UseRefDemo() {
const [count, setCount] = useState(0);
const counterRef = useRef(null);
const increment = () => {
setCount(count + 1);
console.log(counterRef.current);
};
const decrement = () => {
setCount(count - 1);
console.log(counterRef.current);
};
return (
<div>
<button onClick={() => increment()}>Increment</button>
<button onClick={() => decrement()}>Decrement</button>
<div ref={counterRef}>Count is {count}</div>
</div>
);
}
export default UseRefDemo;

7. useReducer

  • A normal “reduce” function takes two values as arguments and returns one single combined value.
  • useReducer hook also takes two arguments — reducer and initial state. The reducer has the current state and based on the action it returns a new state. The useReducer hook returns two values — updated state and a dispatcher which should be invoked to perform an operation and update the state based on the action provided.
import React, { useReducer } from "react";const initialState = { count: 0 };function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
console.log("Out of reach!");
}
}
function UseReducerDemo() {
const [state, dispatcher] = useReducer(reducer, initialState);
return (
<div>
<button onClick={() => dispatcher({ type: "increment" })}>
Increment
</button>
<button onClick={() => dispatcher({ type: "decrement" })}>
Decrement
</button>
<div>Count is {state.count}</div>
</div>
);
}
export default UseReducerDemo;

8. useImperativeHandle

  • It is a hook that provides access of child component’s state and methods to the parent component.
  • It must be used with forwardRef only.
  • Ideally it must be avoided due to imperative code.
import React, { useImperativeHandle, useRef, forwardRef } from "react";function UseImperativeHandleDemo() {
const childRef = useRef(null);
const handleBtnClick = () => {
// invoking child component's method from parent component
childRef.current.print();
};
return (
<>
<button onClick={() => handleBtnClick()}>Parent Div</button>
<Child ref={childRef} />
</>
);
}
const Child = forwardRef((props, ref) => {
useImperativeHandle(ref, () => {
return {
print: () => {
console.log("child method invoked!");
}
};
});
return <button>Child Div</button>;
});
export default UseImperativeHandleDemo;

9. useLayoutEffect

  • It is a hook that is similar to useEffect hook, but the only difference is that it is executed synchronously after the render but before the screen is updated.
  • It takes two arguments — function to be executed and dependencies which need to be changed for the function to be executed.
  • Avoid making use of this hook and instead use the useEffect hook to avoid blocking visual updates.

10. useDebugValue

  • As per the official documentation, it is a hook that is used for displaying label beside custom hooks in React DevTools.
  • It takes any value which we want to display and must not be used in every custom hooks.
import React, { useEffect, useState, useDebugValue } from "react";const useCustomHook = (msg, delay) => {
const [message, setMessage] = useState("");
useEffect(() => {
setTimeout(() => {
setMessage(msg);
}, delay);
});
useDebugValue(message ? "Now set!!" : "Still not set!!");
return message;
};
function UseDebugValueExample() {
const customHookMsg = useCustomHook("Custom hook is working fine!", 3000);
return <div>{customHookMsg}</div>;
}
export default UseDebugValueExample;

That’s all about hooks, folks! Code and demo available here.

In the next post, we will learn about some of the best React practices which we must follow. Stay tuned! Happy Coding!! :)

--

--