React Hooks Simplified

React Hooks Simplified

Unleash the Power of React Hooks

Introduction

In this post, I'll explain how React hooks work.

🪝 What are Hooks?

They're special functions in React that let you do more in functional components. They let you use things like state and effects without needing to write a class.

🔄 useState

It lets you add a state variable to components. To be more specific, it's like our app's memory. It remembers things that change, like a score in a game.

import { useState } from 'react';

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

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

Here the state keeps track of how many times the button has been clicked and saves it as memory.

Whenever it updates, the related section is rerendered. The <p> tag is being rerendered every time the user clicks the button.

It is beneficial to use useState when you want to manage and update state within a functional component.

🧮 useReducer

It lets you add a reducer to your component. So useState is like a notebook, while useReducer is a smart planner.

When useState isn't enough, useReducer comes in and helps. It uses a reducer function and an initial state to handle changes in your app.

A dispatch function also helps you apply those rules and handle state changes.

import { useReducer } from 'react';

function countReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return state + 1;
    case 'decrement':
      return state - 1;
    default:
      throw new Error(`This action type, ${action.type}, doesn't work here.`);
  }
}

function Counter() {
  const [count, dispatch] = useReducer(countReducer, 0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>
        Increase
      </button>
      <button onClick={() => dispatch({ type: 'decrement' })}>
        Decrease
      </button>
    </div>
  );
}

You should use useReducer when you have complex state logic or need to manage multiple state transitions in a component.

📌 useRef

It lets you reference a value that’s not needed for rendering. It's like your app's secret storage.

It stores any value for you and won't trigger a rerender when that value changes.

You should use the hook when you want to keep track of values that can change and stay consistent between component renders.

tsxCopy codeimport { useRef } from 'react';

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

  function focusInput() {
    inputRef.current.focus();
  }

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

Here, useRef remembers the input box. When you click on the button, it knows exactly where to focus because it remembers.

🎬 useEffect

It lets you perform side effects in components. It does stuff after your app makes a move, like after it shows up on the screen.

import { useState, useEffect } from 'react';

function Greeting() {
  const [name, setName] = useState('Miraya');

  // Runs after every rendering (due to no dependency array)
  useEffect(() => {
    document.title = `Hello, ${name}`;
  });

  return (
    <div>
      <p>Hello, {name}</p>
      <button onClick={() => setName('Jane')}>
        Change Name
      </button>
    </div>
  );
}

You should use the useEffect hook when you want to run code or perform actions after a component has rendered.

Dependency Array

It is a way to tell useEffect when it should run again based on certain values from the component.

  • useEffect(fn) runs every time your app redraws.

  • useEffect(fn, []) runs only once when your app first shows up.

  • useEffect(fn, [stuff]) runs every time stuff changes.

🤝 useContext

It lets components share data with each other easily, without needing to pass it through multiple components (props drilling).

import { useContext } from 'react';

// Step 1: Create a context to share the theme color.
const ThemeContext = React.createContext();

// Step 2: Create a component that consumes the theme color from the context.
const ThemedButton = () => {
// Step 3: Access the theme color from the ThemeContext using useContext.
  const themeColor = useContext(ThemeContext);

// Step 4: Apply the theme color to the button style.
  const buttonStyle = {
    background: themeColor,
    color: 'white',
    padding: '10px 20px',
    borderRadius: '5px',
  };

// Step 5: Render the themed button.
  return <button style={buttonStyle}>Click me!</button>;
};

// Step 6: Provide the theme color through the context to the parent component.
export default function App() {
// Step 7: Define the theme color.
  const themeColor = 'blue';

// Step 8: Provide the theme color through the ThemeContext to be accessible by child components.
  return (
    <ThemeContext.Provider value={themeColor}>
{/* Step 9: Render the ThemedButton component. */}
      <ThemedButton />
    </ThemeContext.Provider>
  );
};

You should use the hook when you want to share data across multiple components in your app without passing it down to every component.

🧠 useMemo

It lets you cache the result of a calculation between rerenders.

It's like a chef in your app, it remembers the recipe until the ingredients change.

import { useMemo } from 'react';

function BirthdayCard({ age }) {
  const message = useMemo(() => {
    console.log('Figuring out the message...');
    if(age < 13) return 'Happy Birthday, kiddo!';
    else return 'Happy Birthday!';
  }, [age]);

  return (
    <div>{message}</div>
  );
}

Here useMemo remembers the message based on the age. If the age doesn't change, it just gives back the last message. If the age changes, it figures out the message again. Even if you go back to an old age, a new message is made again

It's used to improve performance by remembering values and avoiding unnecessary calculations.

♻️ useCallback

It lets you cache a function definition between rerenders.

It's like the sticky notes for your functions. It keeps a copy of your function on a sticky note so that it doesn't have to be written again and again.

import { useState, useCallback } from 'react';

function ChildComponent({ onAddPoint }) {
  // Child component logic
  return (
    <button onClick={onAddPoint}>Score a point</button>
  );
}

function CountingGame() {
  const [score, setScore] = useState(0);

  const addPoint = useCallback(() => {
    setScore(prevScore => prevScore + 1);
  }, []);  // Empty array means 'addPoint' will only be written once

  return (
    <div>
      <p>Your score is: {score}</p>
      <ChildComponent onAddPoint={addPoint} />
    </div>
  );
}

Inside useCallback:

  • () => setScore(score => score + 1): This function increases the score when the button is clicked by accessing the current score and adding 1 to it.

  • []: This empty array tells useCallback to remember the function without recreating it when the component rerenders.

You should use the hook to optimize function references and prevent unnecessarily rerenders in child components.

References