Master React

Master React

Top 6 Hooks You Must Know


React Hooks are a feature that allows you to use state and other React capabilities in functional components. They are beneficial because they simplify code by eliminating the need for class components, making it easier to read and maintain. Hooks also enable more direct control over component state and side effects, resulting in more predictable and simpler code.

When using hooks, there are three essential rules to follow:

  1. they can only be called inside React function components

  2. they must be called at the top level of the component, and

  3. they cannot be called conditionally.

These rules ensure that hooks behave consistently and correctly. Additionally, React allows you to create custom hooks also if you need, which enables you to encapsulate and reuse stateful logic across multiple components, enhancing code reusability and modularity. Custom hooks make it easier to share logic without repeating code, keeping your components clean and focused.

In this article, I will explain to you the top 6 most important and used React Hooks which you must know to use React’s maximum potential in your project/app. Every React Developer must know these hooks and how to use them.

useState

One of the most simple and used hooks of all, useState is a hook that lets you manage states(data or properties) in a function component in your application.

The useState Hook can be used to keep track of strings, numbers, booleans, arrays, objects, and any combination of these!

useState accepts an initial state and returns two values:

  • The current state.

  • A function that updates the state.

Initialization and Usage

import { useState } from "react";

function App(){

  const [open, setOpen] = useState(false); // initial state we set false

  return (
      <>
        <button
          type="button"
          onClick={() => setOpen(true)}
        >toggle</button>
      </>
    )
}

open was the current state which we initialized as false, and setOpen was the function that we used in the button to change the state to “true”.

useEffect

useEffect is a React Hook that lets you synchronize a component with an external system. It allows you to perform side-effects in your components like fetching data, directly updating the DOM, and timers.

useEffect accepts two arguments with the 2nd argument being optional.

  • Setup.

  • Dependencies (optional).

Initialization and Usage

import { useState, useEffect } from 'react';

function ExampleComponent() {
  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>
  );
}

export default ExampleComponent;

In this example, useEffect is used to update the document's title whenever the component renders. Each time you click the button, setCount updates the count state, causing the component to re-render, and useEffect runs again, updating the document title to reflect the new count. This is not what we want. There are several ways to control when side effects run.

We should always include the second parameter which accepts an array. We can optionally pass dependencies to useEffect in this array.

  1. No dependency
useEffect(() => {
  //Runs on every render
});

2. With Dependency of Empty Array

useEffect(() => {
  //Runs only on the first render
}, []);

3. With Props or state values:

useEffect(() => {
  //Runs on the first render
  //And any time any dependency value changes
}, [prop, state]);

Effect Cleanup

Some effects require cleanup to reduce memory leaks. Timeouts, subscriptions, event listeners, and other effects that are no longer needed should be disposed of. We do this by including a return function at the end of the useEffect Hook.

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

function ExampleComponent() {
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);

  useEffect(() => {
    // Function to update state when window is resized
    const handleResize = () => {
      setWindowWidth(window.innerWidth);
    };

    // Add event listener for window resize
    window.addEventListener('resize', handleResize);

    // Cleanup function to remove the event listener
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // Empty dependency array means this effect runs once on mount and cleanup on unmount

  return (
    <div>
      <p>Window width: {windowWidth}px</p>
    </div>
  );
}

export default ExampleComponent;

In this example:

  1. useEffect sets up an event listener for the window's resize event.

  2. The handleResize function updates the windowWidth state with the current window width.

  3. The cleanup function returned useEffect removes the resize event listener when the component unmounts or before the effect runs again, preventing potential memory leaks.

This pattern ensures that your component cleans up any resources it uses when it is no longer needed.

useRef

useRef is a React Hook that lets you reference a value that’s not needed for rendering. The useRef Hook allows you to persist values between renders. It can be used to store a mutable value that does not cause a re-render when updated. It can be used to access a DOM element directly.

useRef takes one argument and returns an object with a single property:

  • Argument: initialValue: The value you want the ref object’s current property to be initially. It can be a value of any type. This argument is ignored after the initial render.

  • Returns : current: Initially, it’s set to the initialValue you have passed. You can later set it to something else. If you pass the ref object to React as an ref attribute to a JSX node, React will set its current property. On the next renders, useRef will return the same object.

Initialization and Usage

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

function ExampleComponent() {
  // Create a ref with an initial value of null
  const inputRef = useRef(null);

  // Using useEffect to focus the input element when the component mounts
  useEffect(() => {
    // Focus the input element
    inputRef.current.focus();
  }, []); // Empty dependency array ensures this effect runs only once on mount

  const handleButtonClick = () => {
    // Log the current value of the input element
    console.log(inputRef.current.value);
  };

  return (
    <div>
      {/* Attach the ref to the input element */}
      <input ref={inputRef} type="text" placeholder="Type something" />
      <button onClick={handleButtonClick}>Log Input Value</button>
    </div>
  );
}

export default ExampleComponent;
  • useRef(null) creates a ref object with current property initially set to null.

  • The useEffect hook runs once after the initial render, focusing the input element by accessing inputRef.current.

  • The ref attribute on the <input> element assigns the DOM element to inputRef.current after the component mounts.

  • When the button is clicked, handleButtonClick logs the current value of the input field by accessing inputRef.current.value.

useContext

This Hook is a state management tool basically, it helps you to manage your states globally throughout your application. It can be used together with the useState Hook to share state between deeply nested components more easily than with useState alone.

It simplifies the process of consuming context values, providing an alternative to the traditional method of using the Context.Consumer component.

What useContext Does:

  • Accesses Context: useContext takes a context object (the value returned from React.createContext) as an argument and returns the current context value for that context. This allows you to read the context value directly in your component.

  • Simplifies Code: By using useContext, you avoid the need for nested Context.Consumer components, leading to cleaner and more readable code.

Why useContext is Important:

  • State Sharing: It facilitates state and data sharing across the component tree without having to pass props down manually at every level. This is especially useful for global settings like themes, user authentication, and localization.

  • Improved Readability: The hook provides a more concise and readable way to consume context, reducing boilerplate and making the code easier to understand and maintain.

  • Functional Components: It enables functional components to easily access context values, promoting the use of modern React features and hooks over class components.

Initialization and Usage

  1. Create a Context
import React, { createContext } from 'react';

// Create a context with a default value
const ThemeContext = createContext('light');

export default ThemeContext;

2. Provide a Context Value

import React from 'react';
import ThemeContext from './ThemeContext';
import ThemedButton from './ThemedButton';

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <ThemedButton />
    </ThemeContext.Provider>
  );
}

export default App;

3. Consume the Context Value

import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';

function ThemedButton() {
  // Use the useContext hook to access the current context value
  const theme = useContext(ThemeContext);

  return (
    <button style={{ background: theme === 'dark' ? '#333' : '#FFF', color: theme === 'dark' ? '#FFF' : '#000' }}>
      {`Current theme is ${theme}`}
    </button>
  );
}

export default ThemedButton;
  • Step 1: We create a context using createContext from React. In this example, we create a context for the theme with a default value of 'light'.

  • Step 2: In our App component, we wrap the component tree that needs access to the theme context with ThemeContext.Provider. We provide a value 'dark' to override the default theme.

  • Step 3: In the ThemedButton component, we use the useContext hook to access the current theme value from the ThemeContext. We can then use this value to style the button accordingly.

useMemo

useMemo is a React Hook that lets you cache the result of a calculation between re-renders. It memoizes a value so it doesn’t need to be recalculated. The useMemo Hook only runs when one of its dependencies updates improving performance.

The useMemo Hook can be used to keep expensive, resource-intensive functions from needlessly running.

Initialization and Usage

Let's understand this hook by first seeing the code with this problem and then how useMemo fixes it

Problem:

import React, { useState } from 'react';

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

  // A heavy computation function
  const computeValue = () => {
    let result = 0;
    for (let i = 0; i < 100000000; i++) {
      result += i;
    }
    return result;
  };

  const result = computeValue();

  return (
    <div>
      <p>Result: {result}</p>
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
    </div>
  );
}

export default HeavyComputationComponent;

In this example, computeValue performs a heavy computation every time the component re-renders, even if the count state hasn't changed. This can lead to performance issues, especially if the computation is complex or resource-intensive.

Solution using useMemo:

We can optimize the scenario with useMemo hook to memoize the result of the computation.

import React, { useState, useMemo } from 'react';

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

  // A heavy computation function
  const computeValue = () => {
    let result = 0;
    for (let i = 0; i < 100000000; i++) {
      result += i;
    }
    return result;
  };

  // Memoize the result using useMemo
  const result = useMemo(() => computeValue(), []);

  return (
    <div>
      <p>Result: {result}</p>
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
    </div>
  );
}

export default HeavyComputationComponent;
  • We wrap the heavy computation function computeValue with useMemo.

  • The second argument useMemo is an array of dependencies. If any of these dependencies change, computeValue will be recomputed; otherwise, it will return the memoized result.

  • In this case, the empty dependency array [] ensures that computeValue is only computed once, during the initial render.

  • As a result, the heavy computation is performed only when necessary, improving performance by avoiding unnecessary recalculations on each render.

useCallback

This hook is similar to what useMemo does just here instead of value it returns a memoized function. It takes two arguments, one is the function and second are the dependencies for which you want the re-render to occur.

useCallback(fn, dependencies)

Initialization and Usage

We will understand this hook similar to useMemo with a problem code and then solving it with useCallback.

Problem:

import React, { useState } from 'react';
import ChildComponent from './ChildComponent';

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

  // Callback function passed to ChildComponent
  const handleClick = () => {
    console.log('Button clicked!');
  };

  return (
    <div>
      <ChildComponent onClick={handleClick} />
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
    </div>
  );
}

export default ParentComponent;

In this example, handleClick is recreated on each render of ParentComponent. Every time ParentComponent re-renders, a new reference to handleClick is created, causing the onClick prop passed to ChildComponent to change. This leads to unnecessary re-renders of ChildComponent, even if its props haven't changed.

Solution using useCallback:

import React, { useState, useCallback } from 'react';
import ChildComponent from './ChildComponent';

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

  // Memoize the callback function using useCallback
  const handleClick = useCallback(() => {
    console.log('Button clicked!');
  }, []);

  return (
    <div>
      <ChildComponent onClick={handleClick} />
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
    </div>
  );
}

export default ParentComponent;
  • We wrap the callback function handleClick with useCallback.

  • The second argument of useCallback is an array of dependencies. If any of these dependencies change, the callback function will be recreated; otherwise, it will return the memoized function.

  • In this case, the empty dependency array [] ensures that handleClick is only created once, during the initial render.

  • As a result, the same callback function is passed to ChildComponent on each render of ParentComponent, preventing unnecessary re-renders of ChildComponent unless it's props or state change.

End Note

Well done! You have now understood the react hooks in-depth and also seen the most important hooks and how they work. Now What's left if for you to start implementing these in your React projects to use them to their full potential and make them best in terms of performance and functionality. Try and if you get stuck, come back here to get help.


Thank you for reading! If you have any feedback or notice any mistakes, please feel free to leave a comment below. I’m always looking to improve my writing and value any suggestions you may have. If you’re interested in working together or have any further questions, please don’t hesitate to reach out to me at .

Did you find this article valuable?

Support Faizan's blog by becoming a sponsor. Any amount is appreciated!