Jun 5 2025 ~ 3 min read

React Hooks Fundamentals: State, Effects & Best Practices


React Hooks Fundamentals: State, Effects & Best Practices

🧠 Understanding React State & Hooks

React has a set of functions called hooks that allow you to add additional logic such as state to your components. You can think of state as any information in your UI that changes over time, usually triggered by user interaction.

Key Differences:

  • Props are read-only information that’s passed to components
  • State is information that can change over time, usually triggered by user interaction

Important Rule: You can pass state information to children components as props, but the logic for updating the state should be kept within the component where state was initially created.

Resource: 10 React Hooks Explained

Hook Rules

  • Hooks in React start with the word “use”
  • Can only be used in functional components
  • Must be used right after the function declaration

🐛 Development Mode Behavior

Note: In React development mode (and Next.js), console logs and effects may run twice. This is intentional behavior called “Strict Mode” that helps detect side effects and ensures your components work correctly. This double execution only happens in development - production builds run normally.


🪝 Essential React Hooks

useState - Handle Reactive Data

import { useState } from 'react';

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

  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

Alternative using previous state:

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

  return (
    <button onClick={() => setCount(prev => prev + 1)}>
      {count}
    </button>
  );
}

When count changes, the component will re-render.

Initialization Strategies

Direct Value (re-renders on every change):

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

Function (only renders on first load - better for expensive computations):

const [count, setCount] = useState(() => {
  return 1/2 * 7671265;
});

Updating State Correctly

When updating state multiple times, use the function version to avoid stale state:

const countPlus = () => {
  setCount(prevCount => prevCount + 1);
}

<button onClick={countPlus}>Increment</button>

useEffect - Side Effects & Lifecycle

useEffect allows you to mimic class component lifecycle methods:

// Class component lifecycle equivalent
componentDidMount() {
  // initialized
}

componentDidUpdate() {
  // state updated
}

componentWillUnmount() {
  // destroyed
}

useEffect Patterns

Run on every render:

useEffect(() => {
  alert('hello');
});

Run only once (on mount):

useEffect(() => {
  alert('hello');
}, []); // Empty dependency array

Run when specific state changes:

useEffect(() => {
  alert('count changed');
}, [count]); // Runs when count updates

With cleanup (unmount):

useEffect(() => {
  alert('hello side effect!');

  return () => alert('goodbye component!');
}, []);

This demonstrates cleanup functions:

  • hello side effect! runs when component mounts
  • goodbye component! runs when component unmounts (cleanup phase)

useLayoutEffect

Similar to useEffect but runs synchronously after rendering the component but before painting to the screen. This means the system blocks until the callback finishes execution.

Use case: When you need to read layout properties or make DOM mutations that affect layout before the browser paints.

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

function MeasureComponent() {
  const divRef = useRef(null);
  const [height, setHeight] = useState(0);

  useLayoutEffect(() => {
    // This runs before the browser paints
    if (divRef.current) {
      const rect = divRef.current.getBoundingClientRect();
      setHeight(rect.height);
    }
  }, []);

  return (
    <div ref={divRef}>
      <p>This div's height is: {height}px</p>
    </div>
  );
}

Key difference: useEffect would cause a flicker as the height updates after painting, while useLayoutEffect prevents this by measuring before the browser paints.

Headshot of Alvaro Uribe

Hi, I'm Alvaro. I'm a software engineer from Chile 🇨🇱 based in New Zealand 🇳🇿.