React Hooks Cheat Sheet


Why do you need the React Hooks cheat sheet? In the React world, the introduction to Hooks was a welcome addition by the developer community. Essentially,

Hooks are functions that let you “hook into” React state and lifecycle features from functional components.

They are backward-compatible and don’t work with classes. If you are someone who is new to the world of Hooks or even a seasoned pro, this cheat sheet will help you to get all of your doubts and queries clear.


Basic level - React Hooks Cheat Sheet

1. Why were Hooks introduced, or why do we need them?

The React team introduced Hooks for the following reasons:

  1. To reuse stateful logic without changing the component hierarchy: with Hooks, we can extract stateful logic from a component so that it can be tested independently and reused when needed. This makes it easy to share Hooks among many components or with someone else on the team.
    Patterns like render props and Higher Order Components (HOCs) require you to restructure your components. This can be cumbersome and makes the code harder to follow. Sharing stateful logic became better after Hooks were introduced.

  2. To split one component into smaller functions: Hooks have also made coding lifecycle methods easier than ever. Each of its lifecycle methods often contains a mix of unrelated logic. Components might perform data fetching using componentDidMount(), on the other hand, this method might include some unrelated logic that sets up event listeners.
  3. In many cases, breaking these components into smaller functions is impossible as the stateful logic is all over the place from one lifecycle method to another. For instance, if we need to fetch user data using componentDidMount() in a class component, we would do something like this:

    import React, { Component } from 'react'
    import Axios from 'axios'
    
    export default class Hello extends Component {
    
        constructor(props) {
            super(props);
            this.state = { username: " "};
        }
    
        componentDidMount() {
            Axios.get('/my-api/user/username')
            .then(response => {
                this.setState({ name: response.data.username })
            })
        }
    
        render() {
            return (
                <p>
                    Username is {this.state.username}
                </p>
            )
        }
    }
    

    While if we want to do the same with the built-in React Hooks like useState and useEffect we will do it like:

    import React, { useEffect, useState } from 'react'
    import Axios from 'axios'
    
    export default function Hello() {
    
        const [username, setUsername] = useState(" ")
    
        useEffect(() => {
            Axios.get('/my-api/user/username')
            .then(response => {
                setUsername(response.data.username)
            })
        }, [])
    
        return (
            <p>
                Username is {username}
            </p>
        )
    }
    

    The code written using these two Hooks is more straightforward and concise. Moreover, it's easy to understand and work on if we work in a team. Using lifecycle methods like componentDidMount(), componentDidUpdate(), and componentWillUnmount(), they are handled individually, but when using React Hooks, you can handle all of this with just the useEffect() Hook.


  4. To use more React features without classes: prior to when there was no concept of Hooks in React, its components used mainly class-based components. Making your components with classes all the time required great (and sometimes unnecessary) effort because you always had to switch between the classes, render props, HOCs, etc. Thanks to React Hooks, now you can do all of these without switching using functional components.
  5. Take a look at the two different code snippets where we need to display the classic "Hello, world!" in a div container for the Hello() component:

    • Using class-based component:
    • import React, { Component } from 'react'
      
      export default class Hello extends Component {
          render() {
              return(
                  <div>
                  Hello World!
                  </div>
              )
          }
      }
      

    • Using functional component:
    import React from 'react'
    
    export default function Hello() {
        return (
            <div>
                Hello World!
            </div>
        )
    }
    

    If you compare both of these, the second approach to using a functional component is a significantly simpler code that gets us the same result. You don't need to allocate space to a class instance and then call the render() function. Instead, you simply call the function.

2. The useState Hook

The useState Hook lets you use the local app state within a functional component.

It returns a stateful value along with a function to update it. Here's its basic call signature:

const [state, setState] = useState(initialState);

The setState function is used to update the state, and it accepts a new state value (newState):

setState(newState);

Here are some of the crucial points regarding useState:

  1. Declaring a state variable: to declare a state variable, you just need to call useState with some initial value:
    () => {
      const [count] = useState(100)
      return <div> State variable is {count}</div>
    }
    

  2. Updating a state variable: here, you need to invoke the update function returned by invoking useState:
  3. () => {
      const [items, setItems] = useState(0)
      const handleClick = () => setItems(items + 1)
    
      return (
        <p>
          The user has {items} number of items.
          <p>
            <button onClick={handleClick}>Increase number of items by 1</button>
          </div>
        </div>
      )
    }
    

  4. Using multi-state variables: you can use multiple state variables and update them within a functional component, as shown in this example:
    () => {
      const [items, setItems] = useState(0)
      const [price, setPrice] =
        useState(10)
    
      const handleItems = () => setItems(items + 1)
      const handlePrice = () =>
          setPrice(price + 10)
    
    
      return (
        <div>
          <p>The user has {items} number of items.</p>
          <p>And they sell items worth {price} USD.</p>
    
          <div>
            <button onClick={handleItems}>
              Increase number of items by 1
            </button>
            <button onClick={handlePrice}>
               Increase price by 10
            </button>
          </div>
        </div>
      )
    }
    

  5. Using object state variables: you can also use an entire object as an initial value passed to useState. It will not automatically merge update objects.
    Let's say our initial state is `{ name: "John" }`, then if we use `setState` to add `age`:
    
    
    setState({ age: 'unknown' });
    

    The new state object will become. { name: "John", age: "unknown" }.


  6. Functional useState: the updater function returned after invoking useState can also take a function just like in what we used to do in class-based component's setState:
    const [value, updateValue] = useState(0)
    
    // Either this
     updateValue(1);
    
    // Or this
     updateValue(previousValue => previousValue + 1);
    

    Both these forms are valid for invoking updateValue.

3. The useEffect Hook

The useEffect Hook accepts a function that is used to perform any side effects.

The function passed to useEffect will run after the render is committed to the screen. These effects run after the render is complete, but we can also choose to fire them based on changes in certain values.

Here's the call signature of useEffect:

useEffect(effectFunction, arrayDependencies)

To make a basic side effect, we will be using both useState and useEffect Hooks in the following example:

() => {
  const [age, setAge] = useState(0)
  const handleClick = () => setAge(age + 1)

  useEffect(() => {
    document.title = 'You are ' + age + ' years old!'
  })

  return <div>
    <p> Look at the title of the current tab in your browser </p>
    <button onClick={handleClick}>Update Title</button>
  </div>
}

The useEffect block updates the current tab/browser window's title after running the handleClick function.

The following points are helpful regarding useEffect:

  1. Cleaning up an effect: we must clean up the effect after a period, and this is usually done by returning a function from within the effect function passed to useEffect.
    The following example shows the usage of `useEffect` to create and clean a subscription within an app:
    
    
    useEffect(() => {
      const subscription = props.source.subscribe();
      return () => {
        // Cleaning up the effect
        subscription.unsubscribe();
      };
    });
    

    This cleanup function is run to prevent memory leaks before the component is removed from the UI. Also, if we write a new cleanup, the previous effect is cleaned up before executing the next one.


  2. Creating multiple effects: to add multiple effects, we can have more than one useEffect calls within a functional component, as shown:
    () => {
      // First effect
      useEffect(() => {
        const clicked = () => console.log('window clicked')
        window.addEventListener('click', clicked)
    
        return () => {
          window.removeEventListener('click', clicked)
        }
      }, [])
    
      // Second effect
      useEffect(() => {
        console.log("another useEffect call");
      })
    
      return <div>
        Check your console logs
      </div>
    }
    

  3. Multiple ways to skip effects: we can skip useEffect calls not to invoke it on every render. This can be done in multiple ways:
    • Using an array dependency: here, `useEffect` is passed an array of a value. With this, the effect function will be called while mounting *and* whenever that value we passed is generated. Here's an example:
      () => {
        const [randomNumber, setRandomNumber] = useState(0)
        const [effectLogs, setEffectLogs] = useState([])
      
      // Passing in `randomNumber` in the array dependency
        useEffect(
          () => {
            setEffectLogs(prevEffectLogs => [...prevEffectLogs, 'effect fn has been invoked'])
          },
          [randomNumber]
        )
      
        return (
          <div>
            <h1>{randomNumber}</h1>
            <button
              onClick={() => {
                setRandomNumber(Math.random())
              }}
            >
              Generate random number!
            </button>
            <div>
              {effectLogs.map((effect, index) => (
                <div key={index}>{'🍔'.repeat(index) + effect}</div>
              ))}
            </div>
          </div>
        )
      }

    • Using an empty array dependency: in this case, `useEffect` is passed an empty array `[]`. The effect function is now called only while mounting:
      () => {
        const [randomNumber, setRandomNumber] = useState(0)
        const [effectLogs, setEffectLogs] = useState([])
      
      // Passing an empty array `[]` here
        useEffect(
          () => {
            setEffectLogs(prevEffectLogs => [...prevEffectLogs, 'effect fn has been invoked'])
          },
          []
        )
      
        return (
          <div>
            <h1>{randomNumber}</h1>
            <button
              onClick={() => {
                setRandomNumber(Math.random())
              }}
            >
              Generate random number!
            </button>
            <div>
              {effectLogs.map((effect, index) => (
                <div key={index}>{'🍔'.repeat(index) + effect}</div>
              ))}
            </div>
          </div>
        )
      }

    • Using no array dependency: we can entirely skip effects without providing any array dependency. The effect function will be run after every single render:
      
      useEffect(() => {
        console.log("This will be logged after every render!")
      })

4. Rules of Hooks

To effectively use React Hooks, we need to follow two basic rules when using them:

  1. Call Hooks at the Top Level: this rule states that:

    Don’t call Hooks inside loops, conditions, or nested functions.

    This means you are ensuring that Hooks are called in the same order each time a component renders. This is helpful in preserving your app's state between multiple useState and useEffect calls.

    So you should always make sure to use Hooks at the top level of your function before any early returns. React relies on the order in which Hooks are called, so if we have Hook calls in the same order every time, it looks like this:

    // First render
    useState('Mary')           // 1. Initialize the name state variable with 'Mary'
    useEffect(persistForm)     // 2. Add an effect for persisting the form
    useState('Poppins')        // 3. Initialize the surname state variable with 'Poppins'
    useEffect(updateTitle)     // 4. Add an effect for updating the title
    
    // Second render
    useState('Mary')           // 1. Read the name state variable (argument is ignored)
    useEffect(persistForm)     // 2. Replace the effect for persisting the form
    useState('Poppins')        // 3. Read the surname state variable (argument is ignored)
    useEffect(updateTitle)     // 4. Replace the effect for updating the title
    

    But if we put a Hook call of persistForm inside a condition:

      if (name !== '') {
        useEffect(function persistForm() {
          localStorage.setItem('formData', name);
        });
      }
    

    Then we are breaking the first rule by using a Hook inside a condition! Now the order changes as follows:

    useState('Mary')           // #1 Read the name state variable (argument is ignored)
    // useEffect(persistForm)  // This Hook was skipped!
    useState('Poppins')        // #2 (but was #3). Fail to read the surname state variable
    useEffect(updateTitle)     // #3 (but was #4). Fail to replace the effect
    

    React doesn’t know what to return for the second useState call. Hence multiple Hooks were skipped causing issues.


  2. Only Call Hooks from React Functions: this rule states that:

    Don’t call Hooks from regular JavaScript functions.

    By following this rule, we ensure that all stateful logic code of a component is clearly visible from its source code.

    Instead of calling them from regular functions, we can:

    • Call Hooks from React function components.

    • Call Hooks from custom Hooks.

Intermediate level - React Hooks Cheat Sheet

1. Building custom Hooks

Apart from using the provided useState and useEffect Hooks, we can make a custom Hook as per the need to extract component logic into reusable functions.

Custom Hooks are some functions whose name starts with "use," which may call other Hooks.

It doesn't need to have a specific signature. The following points should be kept in mind when making a custom Hook:

  1. Extracting a custom Hook: custom Hooks are used when we want to share logic between two JavaScript functions and then extract it to a third function. We should make sure to only call other Hooks unconditionally at the top level of the custom Hook.
    Here's an example:
    import { useState, useEffect } from 'react';
    
    // Our custom Hook
    function useFriendStatus(friendID) {  const [isOnline, setIsOnline] = useState(null);
    
      useEffect(() => {
        function handleStatusChange(status) {
          setIsOnline(status.isOnline);
        }
    
        ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
        return () => {
          ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
        };
      });
    
      return isOnline;
    }

    In this example, we see that our useFriendStatus Hook uses the useState and useEffect Hook to function along with some custom code to subscribe to a friend's status.


  2. Using a custom Hook: after making a custom Hook, we can use it anywhere inside our components. For example, if we have the following custom Hook called useBoolean, which returns the state and the functions to update the state in an array:
    const useBoolean = (initialState = false) => {
      const [state, setState] = React.useState(initialState);
    
      const handleTrue = () => setState(true);
      const handleFalse = () => setState(false);
      const handleToggle = () => setState(!state);
    
      return [
        state,
        {
          setTrue: handleTrue,
          setFalse: handleFalse,
          setToggle: handleToggle,
        },
      ];
    };
    

    Note that we are passing the initialState as an argument along with its default value (false). Back in the App component, we can make use of this Hook by passing an initial state to it and by using its returned values to display the state and update it, as shown below:

    function App() {
      const [isToggle, { setToggle }] = useBoolean(false);
    
      return (
        <div>
          <button type="button" onClick={setToggle}>
            Toggle
          </button>
    
          {isToggle.toString()}
        </div>
      );
    }
    

2. The useContext Hook

The useContext Hook saves you from the stress of having to rely on a Context consumer.

It accepts a context object and returns the current context value for that context.

The context object comes from React.createContext, and it's determined by the value prop of the nearest <MyContext.Provider> above the calling component in the tree.

Here's its call signature:

const contextValue = useContext(contextObject)

React's Context is initialized with the createContext top-level API. Here's an example:

import React from 'react';

const CurrencyContext = React.createContext(null);

export { CurrencyContext };

The createContext function takes an initial value which is also the default value if no value prop is defined. We can use it in our example Book component as shown:

const Book = ({ item }) => {
  return (
    <CurrencyContext.Consumer>
      {(currency) => (
        <li>
          {item.title} - {item.price} {currency}
        </li>
      )}
    </CurrencyContext.Consumer>
  );
};

The useState Hook takes the Context as a parameter to retrieve the value from it. Now, if we want to write the above Book component from useContext we end up with the following:

const Book = ({ item }) => {
  const currency = React.useContext(CurrencyContext);

  return (
    <li>
      {item.title} - {item.price} {currency}
    </li>
  );
};

3. The useReducer Hook

The useReducer Hook is an alternative to useState. It is usually preferable when you have a complex state logic that involves multiple sub-values or when the state depends on the previous one.

It has the following call signature:

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

It accepts a reducer of type (state, action) => newState and returns the current state paired with a dispatch method. Here's an example where this Hook is used:

const initialState = { width: 15 }

const reducer = (state, action) => {
  switch (action) {
    case 'plus':
      return { width: state.width + 15 }
    case 'minus':
      return { width: Math.max(state.width - 15, 2) }
    default:
      throw new Error("what's going on?" )
  }
}

const Bar = () => {
  // `useReducer` call
  const [state, dispatch] = useReducer(reducer, initialState)
  return <>
    <div style={{ background: 'teal', height: '30px', width: state.width }}></div>
    <div style={{marginTop: '3rem'}}>
        <button onClick={() => dispatch('plus')}>Increase bar size</button>
        <button onClick={() => dispatch('minus')}>Decrease bar size</button>
    </div>
    </>
}

render(Bar)

We can initialize the state lazily by passing an init function as its third argument. Whatever is returned from this function is returned as the state object. Here's an example to illustrate this:

// Adding a new function
const initializeState = () => ({
  width: 100
})

const initialState = { width: 15 }

const reducer = (state, action) => {
  switch (action) {
    case 'plus':
      return { width: state.width + 15 }
    case 'minus':
      return { width: Math.max(state.width - 15, 2) }
    default:
      throw new Error("what's going on?" )
  }
}

const Bar = () => {
  // Passing `initializeState` as third argument
  const [state, dispatch] = useReducer(reducer, initialState, initializeState)
  return <>
    <div style={{ background: 'teal', height: '30px', width: state.width }}></div>
    <div style={{marginTop: '3rem'}}>
        <button onClick={() => dispatch('plus')}>Increase bar size</button>
        <button onClick={() => dispatch('minus')}>Decrease bar size</button>
    </div>
    </>
}

render(Bar)

4. The useCallback Hook

The useCallback Hook is used to return a memoized version of the passed callback that only changes if one of the dependencies has changed.

It's useful when passing callbacks to optimized child components to prevent unnecessary renders. Here's its call signature:

const memoizedCallback = useCallback(function, arrayDependency)

To use useCallback with an inline function, we can call it as follows:

const App = () => {
  const [age, setAge] = useState(99)
  const handleClick = () => setAge(age + 1)
  const someValue = "someValue"

  return (
    <div>
      <Age age={age} handleClick={handleClick} />

      // Inline call to `useCallback`
      <Instructions doSomething={useCallback(() => {
        return someValue
      }, [someValue])} />
    </div>
  )
}

Here, the <Instructions /> child component has the doSomething prop, which is passed with a call to the useCallback Hook.

5. The useMemo Hook

The useMemo Hook allows memoizing expensive functions to avoid calling them on every render.

You pass a "create" function and an array of dependencies, and it will spit out its memoized value. This Hook has the following call signature:

const memoizedValue = useMemo(compute, dependencies)

The following points should be kept in mind when using the useMemo Hook:

  1. It will only recompute the memoized value when one of the dependencies you pass has changed.
  2. We should not add side effects to its call; they should instead belong to the useEffect Hook.
  3. During the initial rendering, useMemo invokes compute, memoizes the result, and returns to its component.
  4. If we don't provide any array, a new value will be computed on every render of the component where useMemo is used.

Let's say we have a <CalculateFactorial /> component:

import { useState } from 'react';

export function CalculateFactorial() {
 const [number, setNumber] = useState(1);
 const [inc, setInc] = useState(0);

 const factorial = factorialOf(number);

 const onChange = event => {
   setNumber(Number(event.target.value));
 };

 const onClick = () => setInc(i => i + 1);

 return (
   <div>
     Factorial of
     <input type="number" value={number} onChange={onChange} />
     is {factorial}
     <button onClick={onClick}>Re-render</button>
   </div>
 );
}

function factorialOf(n) {
 console.log('factorialOf(n) called!');
 return n <= 0 ? 1 : n * factorialOf(n - 1);
}

We can memoize the factorial calculation here whenever the component re-renders by using useMemo(() => factorialOf(number), [number]) as shown in the updated code below:

...
export function CalculateFactorial() {
 const [number, setNumber] = useState(1);
 const [inc, setInc] = useState(0);

// `useMemo` usage
const factorial = useMemo(() => factorialOf(number), [number]);

 const onChange = event => {
   setNumber(Number(event.target.value));
 };

 const onClick = () => setInc(i => i + 1);

 return (
   <div>
     Factorial of
     <input type="number" value={number} onChange={onChange} />
     is {factorial}
     <button onClick={onClick}>Re-render</button>
   </div>
 );
}

function factorialOf(n) {
 console.log('factorialOf(n) called!');
 return n <= 0 ? 1 : n * factorialOf(n - 1);
}

6. The useRef Hook

The useRef Hook returns a mutable ref object whose .current property is initialized to the passed argument (initialValue).

It has the following call signature:

const refContainer = useRef(initialValue);

The following points are essential about useRef:

  1. useRef acts as a "box" that can hold a mutable value in its .current property.
  2. This Hook creates a plain JavaScript object. The difference between useRef() and creating a { current: ... } object yourself is that useRef gives you the same ref object on every render.
  3. If you mutate the .current property, it will not cause a re-render. The Hook doesn't notify you when its content changes.
  4. The value of the reference is persisted, i.e., it stays the same between component re-renderings.
  5. References can also access DOM elements. For that, we need to refer to the ref attribute of the element you would like to access.

Here's an example where we need to access the DOM element to focus on the input field when the component mounts:

import {useRef, useEffect} from 'react';

function InputFocus() {

  // `useRef` implementation
  const inputRef = useRef();

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return (
    <input
      ref={inputRef}
      type="text"
    />
  );
}

After creating a reference to hold the input element in inputRef , it's assigned to the input field's ref attribute. After mounting, React sets inputRef.current to be the input element.

Advance level - React Hooks Cheat Sheet

1. Testing React Hooks

If your testing solution doesn’t rely on React internals, then testing components with Hooks isn't different from how we usually test components.

Let's assume we have the following component, which counts the number of times you clicked the button:

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

We can now test this using React DOM or the React Testing Library to reduce the boilerplate code. But let's see how to test it with vanilla code. To make sure the behavior matches with what happens in the browser, we will wrap the code rendering and updating into multiple ReactTestUtils.act() calls as shown:

// counter.test.js

import { act } from 'react-dom/test-utils';
import Counter from './Counter';

let container;

beforeEach(() => {
  container = document.createElement('div');
  document.body.appendChild(container);
});

afterEach(() => {
  document.body.removeChild(container);
  container = null;
});

it('can render and update a counter', () => {
  // Test first render and effect
  act(() => {
    ReactDOM.createRoot(container).render(<Counter />);
  });

  const button = container.querySelector('button');
  const label = container.querySelector('p');
  expect(label.textContent).toBe('You clicked 0 times');
  expect(document.title).toBe('You clicked 0 times');

  // Test second render and effect
  act(() => {
    button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
  });

  expect(label.textContent).toBe('You clicked 1 times');
  expect(document.title).toBe('You clicked 1 times');
});

2. Data fetching with React Hooks

Data fetching is quite a typical pattern we see when working with a React app. But if you are using Hooks in a particular app component, then here's how you should fetch the data.

First, let's say we have this basic App component that shows a list of items:

import React, { useState } from 'react';

function App() {
  const [data, setData] = useState({ hits: [] });

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a target="_blank" href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

Both the state and the state update function come from the useState Hook. Now, let's use the Axios library to fetch data here. We can implement our effect Hook as follows:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });

  useEffect(async () => {
    // Using Axios to fetch data from an API endpoint
    const result = await axios(
      'https://hn.algolia.com/api/v1/search?query=redux',
    );

    setData(result.data);
  });

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a target="_blank" href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

But if you run the app, you will see the code is stuck in a loop. The effect Hook runs when the component mounts but also when it updates. We are setting the state after every data fetch. To fix this, we only want to fetch the data when the component mounts.

Hence, we should provide an empty array as the second argument to the useEffect Hook as shown:

...
useEffect(async () => {
    const result = await axios(
      'https://hn.algolia.com/api/v1/search?query=redux',
    );

    setData(result.data);
  }, []);
...

To further improve the code quality, we can use an async function inside useEffect so that it returns a cleanup function in the Promise. So our final code will be:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        'https://hn.algolia.com/api/v1/search?query=redux',
      );

      setData(result.data);
    };

    fetchData();
  }, []);

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a target="_blank" href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

3. Creating a loading indicator with Hooks

Using the same data loading example as above, we can start implementing a basic loading indicator. For this, we will add another state value using the useState Hook:

const [isLoading, setIsLoading] = useState(false);

Now inside of our useEffect call, we can set the toggle for the true and false values for setIsLoading:

useEffect(() => {
  const fetchData = async () => {
    setIsLoading(true);

    const result = await axios(url);

    setData(result.data);
    setIsLoading(false);
  };

    fetchData();
}, [url]);

Once the effect is called for data fetching when the component mounts or the URL state changes, the loading state is set to true. Here's the entire code after these changes:

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    'https://hn.algolia.com/api/v1/search?query=redux',
  );
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsLoading(true);

      const result = await axios(url);

      setData(result.data);
      setIsLoading(false);
    };

    fetchData();
  }, [url]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button
        type="button"
        onClick={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        Search
      </button>

      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {data.hits.map(item => (
            <li key={item.objectID}>
              <a target="_blank" href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}

4. Data aborting using Hooks

We often see that the component state is set even though the component was already mounted. Due to this, we also want to abort data fetching in our component so that we don't encounter any bugs or loops.

We can use the effect Hook, which comes with a cleanup function and runs when a component unmounts. Let's use it to prevent data fetching in the following example:

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData,
  });

  useEffect(() => {
    let didCancel = false;

    const fetchData = async () => {
      dispatch({ type: 'FETCH_INIT' });

      try {
        const result = await axios(url);

        if (!didCancel) {
          dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
        }
      } catch (error) {
        if (!didCancel) {
          dispatch({ type: 'FETCH_FAILURE' });
        }
      }
    };

    fetchData();

    return () => {
      didCancel = true;
    };
  }, [url]);

  return [state, setUrl];
};

Here, we used a boolean called didCancel so that the data fetching code knows about the component's state (mounted or unmounted). If it unmounts, the flag is set to true, which prevents setting the component state after the data fetching has been resolved asynchronously.

5. Getting previous props or states with Hooks

Sometimes we need previous props of a component to clean up a used effect. To illustrate this, let's say we have an effect that subscribes to a socket based on the userId props. If this prop changes, we want to unsubscribe from the previously set userId and then subscribe to the next one.

For this, we can use a cleanup function with the useEffect function as:

useEffect(() => {
  ChatAPI.subscribeToSocket(props.userId);
  return () => ChatAPI.unsubscribeFromSocket(props.userId);
}, [props.userId]);

Here, first the ChatAPI.unsubscribeFromSocket(3) will run and then ChatAPI.unsubscribeFromSocket(4) in order when userId changes. Hence, the cleanup function does the work for us. It captures the previous userId in a closure.


Conclusion - React Hooks Cheat Sheet

In this React Hooks cheat sheet, we covered all the major topics related to Hooks. From why it is needed, to how to unlock Hook's superpowers to manage and handle your app state effectively, everything is covered with relevant examples.

We hope this cheat sheet will help you in your future or previous React applications.