React Hooks: Complete Interview Guide.

Sourav Saha
7 min readSep 25, 2021

--

Hooks were introduced in React v16.8, they let us use state, lifecycle methods and other React features without ever writing a class component.

Hooks are here to stay, and as more and more companies are migrating from class based to Hooks way, In this article I’ll share the frequently asked interview questions from React Hooks.

Why were Hooks introduced in React in first place?

There are three main problem with React that Hooks are trying to solve.

  1. Understanding Classes: Classes are tricky to understand in JS and the way binding and “this” keyword works is also pretty confusing. Classes are not just hard for humans to understand it’s also hard for machines too. If you look at the minified files (minimizing code and markup in your web pages and script files), all the method names are unminified, The unused methods are not ripped out that because it’s hard to tell at compiled time exactly how all the methods fit together. Classes are also tough for hot reloading and make it harder for compiler to optimize.
  2. Separation Of Logic: A large component whose logic is sort of tangle mess as each individual task/logic is split across different lifecycle methods, and tracking down each of the logic becomes difficult if your component grows in size.
  3. Code Sharing: Before custom hooks, a component forms the foundation of the two main pattern of sharing code between react apps they are higher order components and render props, both of these pattern comes with significant downside, you need to restructure your app anytime you need to pull one of these in, In more complicated example these leads to a “Wrapper Hell” where the nesting becomes complicated as data flows throughout the app.

What are the rules with React Hooks?

  1. Hooks can only be called at top level.
  2. Don’t call Hooks inside loops, conditions or nested functions.
  3. Call Hooks from React Component or Custom Hooks.

How do you use Lifecycle methods in Hooks?

There are three stages of Lifecycle in React.

React Lifecycle in Class based Components.
  1. Initial render /Mounting: In Class based components we have componentDidMount, which runs after the initial render. To replace this in Hooks we have: useEffect without any dependencies ([]).
import { useEffect } from “react”;

const App = () => {
useEffect(() => {
console.log(“Runs once after initial render “);
}, []);

return <h1>UseEffect on Initial Render</h1>;
};

Passing in an empty array [] of inputs tells React that your effect doesn’t depend on any values from the component, so that effect would run only on mount and clean up on unmount; it won’t run on updates. But unlike componentDidMount, it will capture props and state. So even inside the callbacks, you’ll see the initial props and state.

2. Updating: In updating phase as a replacement for componentDidUpdate, we have the same hook, useEffect but this the we won’t be passing any 2nd arguments to it.

import { useEffect } from “react”;

const App = () => {
useEffect(() => {
console.log(“Runs whenever the component receives new state or props.”);
});

return <h1>Use Effect on Update</h1>;
};

But problem with this code is that it will run every time, even on the initial render, if you want your code to run only when a specific dependency has changed then you need to pass it as its 2nd argument.

import { useState, useEffect } from “react”;

const App = ({prop}) => {
const [state, setState] = useState(‘’);
useEffect(() => {
console.log(“Runs once after initial rendering and after every rendering if prop or state changes”);
},[prop,state]);

return <h1>Use Effect on Update</h1>;
};

3. Unmounting: In unmounting we have only one lifecycle method componentWillUnmount and we can achieve that in Hooks by returning a function from useEffect, it will act as an effect cleanup.

import { useEffect } from “react”;

const App = ({prop}) => {
useEffect(() => {
const timer = setInterval(() => {
console.log(“Runs each second”);
}, 1000);
return () => {
console.log(“Cleanup is done here”);
clearInterval(timer);
};
}, [prop]);

return <h1>Use Effect on Unmount</h1>;
};

How are setState and useState different.

Immutability, the setter function returned by useState doesn’t merge objects like setState() does in class components. Instead you should create a copy of the object and then update the state in useState setter function.

import React, { useState } from “react”;export default function App() {const [personObj, setPerson] = useState({name: “”,address: {city: “”,street: “”,},});function correctWayToSetState() {setPerson((prevState) => ({…prevState, // copy all other field/objectsaddress: {// recreate the object that contains the field to update…prevState.city, // copy all the fields of the objectstreet: “New Street”, // overwrite the value of the field to update},}));}

How to use previous state in useState.

useState setter function doesn’t update the value right away. Rather, it enqueues the update operation. Then, after re-rendering the component, the argument of useState will be ignored and this function will return the most recent value.
If you use the previous value to update state, you must pass a function that receives the previous value and returns the new value.

import React, { useState } from “react”;export default function App() {const [count, setCount] = useState(0);function incrementCountHandler() {setCount((prevCount) => prevCount + 1);}return (<><button onClick={incrementCountHandler}>Increment Count</button>{count}</>);}

What’s the difference between useCallback and useMemo

Both these Hooks are based on the memoization concept of JavaScript and prevent unnecessary re-renders, making your code way more efficient.

While both useMemo and useCallback memoize something between renders until the dependencies change, the difference is just what they memoize, useCallback returns a memoized callback and useMemo returns a memoized value. In other words, useCallback gives you referential equality (Some value is comparably the same whenever you reference it) between renders for functions. And useMemo gives you referential equality between renders for values.

useCallback:

function App({ userId }) {const fetchData = useCallback(() => {
const res = //some data from Api call
setUser(res);
}, [userId]);
useEffect(() => {
fetchData();
}, [fetchData]); // Here the fetchData function will remain the same on re-render and will only change when userId changes.
return <h1> useCallback </h1>
}

useMemo:

import { massiveArray } from “./largeArray”function App({ userId }) {
const largeArray = useMemo(() => filterLargeArray(myNumber),
[myNumber]
)
function filterLargeArray(data){
console.log(“Expensive Computation”)
return massiveArray.filter…..
}
//When the userId changes the function will run and useMemo will remember the result so we do not have to run that function on each render which will end up saving us a lot of time and resources.

What is useReducer and when to use it instead of useState.

It’s a Hook for state management, alternative to useState (useState is built using useReducer).

It accepts 3 arguments:

  1. Reducer function: Which changes the state based on action.
  2. Initial State.
  3. init function(optional) : If you want to set initial state lazily.

useReducer returns 2 things:

  1. The state.
  2. A dispatch function which is called when you want to update the state with the action argument.
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:
throw new Error();
}
}

function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

Write a custom hook to fetch data from a API

import { useState, useEffect } from 'react';

function useFetch(url) { //Note all custom Hooks starts with use keyword
const [data, setData] = useState(null);
const [loading, setLoading] = useState(null);
const [error, setError] = useState(null);

useEffect(() => {
setLoading('loading...')
fetch(url).then(res => res.json())
.then(data => {
setLoading(false);
setData(data);
})
.catch(err => {
setLoading(false)
setError('An error occurred. Awkward..')
})
}, [url])

return { data, loading, error }
}
export default useFetch;

How to use ref’s in Hooks.

Refs provide a way to access DOM nodes or React elements created in the render method. React provided a hooks called useRef to acces refs in functional component.

The difference between createRef and useRef is that createRef will always create a new ref. In a class-based component, you would typically put the ref in an instance property during construction (e.g. this.input = createRef()). You don't have this option in a function component. useRef takes care of returning the same ref each time as on the initial rendering.

It saves its value between re-renders in a functional component and doesn’t create a new instance of the ref for every re-render.

That’s all folks……

Follow me on LinkedIn at https://www.linkedin.com/in/sourav-saha-66b37b92/

--

--