useHooks(🐠)

Easy to understand React Hook recipes by ui.dev
What's all this about?

Hooks are a new addition in React that lets you use state and other React features without writing a class. This website provides easy to understand code examples to help you learn how hooks work and inspire you to take advantage of them in your next project.

📩  Get new recipes in your inbox
Join 7,031 subscribers. No spam ever.

useChange

If you need to track changes of some variable, you can use useEffect hook, but if you need to run callback with unstable reference (i.e. it came from props), you might want to omit it from dependencies of hook. This approach is unsafe. To implement this kind of logic you should use usePrevious hook and manually compare values like in componentDidUpdate lifecycle method from class components.

import { useState, useEffect, useRef } from "react";

function usePrevious(value) {
  const previousRef = useRef();

  useEffect(() => {
    previousRef.current = value;
  }, [value]);

  return previousRef.current;
}

// Hook
function useChange(
  callback,
  value
) {
  const previousValue = usePrevious(value);

  useEffect(() => {
    if (value !== previousValue) {
      // Callback called only when value is changed
      callback(previousValue, value);
    }
  }, [value, previousValue, callback]);
}

// Usage
function App() {
  // State value and setter for our example
  const [count, setCount] = useState(0),
    [list, setList] = useState([]);

  // Function with unstable ref (new one is created on every render)
  const addListItem = (item) => {
    setList((list) => [item, ...list]);
  };

  useChange((prev, curr) => {
    const changeString = `${prev} -> ${curr}`;
    // It's safe to use unstable variables inside useChange
    addListItem(changeString);
    console.log(`Change encountered: ${changeString}`);
  }, count);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <p>Count: {count}</p>
      <p>Changes:</p>
      <ul>
        {list.map((item) => (
          <li>{item}</li>
        ))}
      </ul>
    </div>
  );
}
import { useState, useEffect, useRef } from "react";

function usePrevious<T>(value: T): T | undefined {
  const previousRef = useRef<T>();

  useEffect(() => {
    previousRef.current = value;
  }, [value]);

  return previousRef.current;
}

// Hook
function useChange<T>(
  callback: (prev: T | undefined, curr: T) => void,
  value: T
): void {
  const previousValue = usePrevious(value);

  useEffect(() => {
    if (value !== previousValue) {
      // Callback called only when value is changed
      callback(previousValue, value);
    }
  }, [value, previousValue, callback]);
}

// Usage
function App() {
  // State value and setter for our example
  const [count, setCount] = useState<number>(0),
    [list, setList] = useState<string[]>([]);

  // Function with unstable ref (new one is created on every render)
  const addListItem = (item: string) => {
    setList((list) => [item, ...list]);
  };

  useChange((prev, curr) => {
    const changeString = `${prev} -> ${curr}`;
    // It's safe to use unstable variables inside useChange
    addListItem(changeString);
    console.log(`Change encountered: ${changeString}`);
  }, count);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <p>Count: {count}</p>
      <p>Changes:</p>
      <ul>
        {list.map((item) => (
          <li>{item}</li>
        ))}
      </ul>
    </div>
  );
}
Next recipe: