React
01 / 07

Hooks Deep Dive

React Hooks Deep Dive

Comprehensive guide to React hooks - the modern way to manage state and side effects in functional components:

useState Hook

useState is the most fundamental hook for managing local component state. It returns a stateful value and a function to update it.

function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      <input 
        value={name} 
        onChange={(e) => setName(e.target.value)} 
        placeholder="Enter name"
      />
    </div>
  );
}

Functional Updates

When the new state depends on the previous state, use a function to ensure you get the latest value:

// Good - functional update
setCount(prevCount => prevCount + 1);

// Avoid - might use stale state
setCount(count + 1);

useEffect Hook

useEffect lets you perform side effects in functional components. It combines componentDidMount, componentDidUpdate, and componentWillUnmount.

Basic useEffect

useEffect(() => {
  // Runs after every render
  document.title = `Count: ${count}`;
});

useEffect with Dependencies

useEffect(() => {
  // Runs only when count changes
  document.title = `Count: ${count}`;
}, [count]);

useEffect with Cleanup

useEffect(() => {
  const timer = setInterval(() => {
    setCount(prev => prev + 1);
  }, 1000);
  
  return () => {
    clearInterval(timer);
  };
}, []);

Mount/Unmount Effect

useEffect(() => {
  // Runs only on mount
  console.log('Component mounted');
  
  return () => {
    // Runs on unmount
    console.log('Component unmounted');
  };
}, []);

useContext Hook

useContext provides a way to pass data through the component tree without having to pass props down manually at every level.

// Create context
const ThemeContext = createContext();

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

// Consumer component
function Header() {
  const theme = useContext(ThemeContext);
  return <h1 className={theme}>Header</h1>;
}

useReducer Hook

useReducer is an alternative to useState for managing complex state logic. It follows the reducer pattern and is useful when state updates depend on previous state.

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 (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </div>
  );
}

useMemo Hook

useMemo memoizes the result of a computation and only recalculates when dependencies change. It helps optimize expensive calculations.

function ExpensiveComponent({ items }) {
  const expensiveValue = useMemo(() => {
    return items.reduce((sum, item) => sum + item.value, 0);
  }, [items]);
  
  return <div>Total: {expensiveValue}</div>;
}

useCallback Hook

useCallback memoizes a function and only creates a new function when dependencies change. It helps prevent unnecessary re-renders of child components.

function Parent({ items }) {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <Child onClick={handleClick} />
    </div>
  );
}

useRef Hook

useRef returns a mutable ref object that persists for the full lifetime of the component. It can be used to access DOM elements or store mutable values.

function TextInput() {
  const inputRef = useRef(null);
  
  const focusInput = () => {
    inputRef.current.focus();
  };
  
  return (
    <div>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

Custom Hooks

Custom hooks are JavaScript functions that start with "use" and can call other hooks. They allow you to extract component logic into reusable functions.

// Custom hook
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);
  
  const increment = useCallback(() => setCount(c => c + 1), []);
  const decrement = useCallback(() => setCount(c => c - 1), []);
  const reset = useCallback(() => setCount(initialValue), [initialValue]);
  
  return { count, increment, decrement, reset };
}

// Using the custom hook
function Counter() {
  const { count, increment, decrement, reset } = useCounter(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

Hook Rules

  • Only call hooks at the top level of React functions

  • Don't call hooks inside loops, conditions, or nested functions

  • Only call hooks from React function components or custom hooks

Common Hook Patterns

Data Fetching Hook

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err);
        setLoading(false);
      });
  }, [url]);
  
  return { data, loading, error };
}

Local Storage Hook

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });
  
  const setValue = (value) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };
  
  return [storedValue, setValue];
}

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free