Skip to content
⚠️ This article was written in 2020. Some content may be outdated.

React useReducer Primer: Complex State Management in the Hooks Era

React's useReducer hook is being used more and more for state management in everyday development. This article systematically covers its usage, internals, and optimization strategies.

Quick Start ​

Here is a more complete example of real-world usage:

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

function useIntersectionObserver(options = {}) {
  const [isVisible, setIsVisible] = useState(false);
  const ref = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        setIsVisible(entry.isIntersecting);
      },
      { threshold: 0.1, ...options },
    );
    const el = ref.current;
    if (el) observer.observe(el);
    return () => {
      if (el) observer.unobserve(el);
    };
  }, []);

  return [ref, isVisible];
}

This approach improves both the testability and the extensibility of the code.

Internals ​

A complete example:

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

function useIntersectionObserver(options = {}) {
  const [isVisible, setIsVisible] = useState(false);
  const ref = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        setIsVisible(entry.isIntersecting);
      },
      { threshold: 0.1, ...options },
    );
    const el = ref.current;
    if (el) observer.observe(el);
    return () => {
      if (el) observer.unobserve(el);
    };
  }, []);

  return [ref, isVisible];
}

Pay attention to edge-case handling — it is critical in production environments.

Real-world Application ​

The key is understanding the core logic:

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

function useIntersectionObserver(options = {}) {
  const [isVisible, setIsVisible] = useState(false);
  const ref = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        setIsVisible(entry.isIntersecting);
      },
      { threshold: 0.1, ...options },
    );
    const el = ref.current;
    if (el) observer.observe(el);
    return () => {
      if (el) observer.unobserve(el);
    };
  }, []);

  return [ref, isVisible];
}

Performance optimization has to fit the context — not every situation calls for micro-optimization.

Performance Comparison ​

Here is one way to improve on it:

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

function useIntersectionObserver(options = {}) {
  const [isVisible, setIsVisible] = useState(false);
  const ref = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        setIsVisible(entry.isIntersecting);
      },
      { threshold: 0.1, ...options },
    );
    const el = ref.current;
    if (el) observer.observe(el);
    return () => {
      if (el) observer.unobserve(el);
    };
  }, []);

  return [ref, isVisible];
}

This solution has been running stably in production for over six months and has been validated in practice.

Summary ​

  • Code examples are for reference only — adjust them to your specific business scenario

MIT Licensed