As developers, we’re constantly striving to write cleaner, more efficient code—and React Hooks are an essential part of that journey. Introduced in React 16.8, hooks let us use state and other React features without writing a class. In this post, we’ll dive into some of the most commonly used hooks: useState, useEffect, useCallback, useMemo, useReducer, useTransition, and useLayoutEffect.

1. useState: Managing Local Component State

The useState hook allows us to add local state to functional components.

import React, { 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>
  );
}

2. useEffect: Handling Side Effects

useEffect lets us perform side effects in function components—such as fetching data, updating the DOM, or setting up subscriptions.

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

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  return <p>Time: {seconds}s</p>;
}

3. useCallback: Memoizing Functions

useCallback returns a memoized version of a callback that only changes if one of its dependencies does.

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

function ExpensiveComponent({ onClick }) {
  console.log("Rendering ExpensiveComponent");
  return <button onClick={onClick}>Click me</button>;
}

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

  const handleClick = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);

  return <ExpensiveComponent onClick={handleClick} />;
}

4. useMemo: Optimizing Expensive Calculations

useMemo memoizes the result of an expensive calculation.

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

function App() {
  const [count, setCount] = useState(0);
  const expensiveValue = useMemo(() => {
    console.log("Calculating...");
    return count * 2;
  }, [count]);

  return (
    <div>
      <p>Expensive Value: {expensiveValue}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

5. useReducer: State Management Alternative

useReducer is often preferred over useState when managing complex state logic.

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:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  );
}

6. useTransition: Managing Concurrent UI Updates

useTransition helps keep the UI responsive during heavy state transitions by marking updates as non-urgent.

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

function List({ items }) {
  return items.map((item, index) => <div key={index}>{item}</div>);
}

function App() {
  const [input, setInput] = useState('');
  const [list, setList] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    const value = e.target.value;
    setInput(value);

    startTransition(() => {
      const newList = Array.from({ length: 10000 }, (_, i) => `${value} ${i}`);
      setList(newList);
    });
  };

  return (
    <>
      <input type="text" value={input} onChange={handleChange} />
      {isPending ? <p>Loading...</p> : <List items={list} />}
    </>
  );
}

7. useLayoutEffect: DOM Mutations Before Paint

useLayoutEffect works like useEffect, but it fires synchronously after all DOM mutations. It’s useful when we need to measure DOM nodes or perform layout calculations.

import React, { useLayoutEffect, useRef, useState } from 'react';

function Box() {
  const boxRef = useRef(null);
  const [width, setWidth] = useState(0);

  useLayoutEffect(() => {
    if (boxRef.current) {
      setWidth(boxRef.current.getBoundingClientRect().width);
    }
  }, []);

  return (
    <div ref={boxRef} style={{ width: '50%', background: 'skyblue' }}>
      <p>Width: {width}px</p>
    </div>
  );
}

Conclusion

Hooks have changed the way we write React components. They allow us to write more concise, readable code while unlocking powerful features. As we continue to build rich user interfaces, mastering these basic hooks will give us the tools we need to work efficiently and effectively in modern React apps.

Leave a Reply

Your email address will not be published. Required fields are marked *