Mastering React Hooks: Best Practices and Streamlined Patterns

This article reviews React Hooks, compares them with class components, demonstrates change‑driven coding with practical examples, introduces marble diagrams for visualizing state flows, and explores advanced patterns like custom hooks, immutable data handling, and global state management for more maintainable frontend development.

ELab Team
ELab Team
ELab Team
Mastering React Hooks: Best Practices and Streamlined Patterns

Review of React Hooks

React Hooks can be seen as a new framework that replaces class‑based components with function components driven by useState and useEffect.

Hooks as a New Framework

Hooks framework
Hooks framework

Top‑down flow : Logic proceeds linearly rather than inter‑method calls.

Weakened handlers : Callbacks are less natural than class methods.

Simplified lifecycle : useEffect replaces multiple lifecycle methods.

Distributed state/effect registration : State can be declared where needed.

Dependency‑driven : Hooks accept a deps array to trigger updates.

Because of these characteristics, Hooks should be written in a "change‑driven" style rather than a callback‑driven one.

Example 1: Declaring a Request with useEffect

Callback‑style (class‑like) implementation

const Demo: React.FC = () => {
  const [state, setState] = useState({ keyword: '' });
  const query = useCallback((queryState) => {
    // ...
  }, []);
  const handleKeywordChange = useCallback((e) => {
    const latestState = { ...state, keyword: e.target.value };
    setState(latestState);
    query(latestState);
  }, [state, query]);
  return // view
}

Problems: stale state, manual dependency management, unclear responsibilities.

Change‑driven implementation

const Demo: React.FC = () => {
  const [state, setState] = useState({ keyword: '' });
  const handleKeywordChange = useCallback((e) => {
    const nextKeyword = e.target.value;
    setState(prev => ({ ...prev, keyword: nextKeyword }));
  }, []);
  useEffect(() => {
    // query
  }, [state]);
  return // view
}

This version lets the query run automatically whenever state changes, eliminating manual calls.

Example 2: Registering a Window‑size Listener

Callback‑style

const Demo: FC = () => {
  const callback = // ...
  useEffect(() => {
    window.addEventListener('resize', callback);
    return () => window.removeEventListener('resize', callback);
  }, []);
  return // view
}

The callback reference changes on each render, causing stale listeners.

Change‑driven implementation

const Demo: FC = () => {
  const [windowSize, setWindowSize] = useState([window.innerWidth, window.innerHeight] as const);
  useEffect(() => {
    const handleResize = () => {
      setWindowSize([window.innerWidth, window.innerHeight]);
    };
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  const callback = // ...
  useEffect(callback, [windowSize]);
  return // view
};

By turning the size into state, the effect that depends on it runs declaratively.

The key of the change‑driven approach is converting actions into state .

Marble Diagrams

Marble diagrams visualize data streams; each marble represents a value emitted over time, similar to how Hook state changes can be viewed as a stream.

RxMarble example
RxMarble example

Immutable Data Flow and Execution Frames

State updates should be immutable so that reference changes match value changes. Execution frames correspond to each render triggered by state or prop changes.

Immutable Data Flow

Ensure every setState produces a new object, optionally using utilities or libraries like ImmutableJS.

Execution Frame

When state or props change, React schedules a render frame; each marble aligns with these frames.

Sources of Change

Props change

Event (e.g., window resize)

Scheduler (animationFrame, interval, timeout)

Packaging an Event

const useClickEvent = () => {
  const [clickEvent, setClickEvent] = useState<{x:number; y:number}>(null);
  const dispatch = useCallback((e) => {
    setClickEvent({ x: e.clientX, y: e.clientY });
  }, []);
  return [clickEvent, dispatch] as const;
};

Packaging a Scheduler (interval)

const useInterval = (interval) => {
  const [count, setCount] = useState();
  useEffect(() => {
    const id = setInterval(() => setCount(c => c + 1), interval);
    return () => clearInterval(id);
  }, []);
  return count;
};

Stream‑like Operators in Hooks

Mapping, skipping, taking, debounce, throttle, and async flows can be expressed with custom hooks.

Map (useMemo)

const [state1, setState1] = useState(initial1);
const [state2, setState2] = useState(initial2);
const computedState = useMemo(() => {
  return Array(state2).fill(state1).join('');
}, [state1, state2]);

Skip / Take (useCountMemo, useCountEffect)

const useCountMemo = <T>(callback: (count:number)=>T, deps:any[]):T => {
  const countRef = useRef(0);
  return useMemo(() => {
    const val = callback(countRef.current);
    countRef.current++;
    return val;
  }, deps);
};

Debounce / Throttle

const useDebounce = (value, time=250) => {
  const [debounced, setDebounced] = useState(null);
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), time);
    return () => clearTimeout(timer);
  }, [value]);
  return debounced;
};

Action / Reducer Async Flow (useAsync)

const responseState = useAsync(initial, actionState, function* (action, prev) {
  switch(action?.type) {
    case 'clear': return null;
    case 'request': {
      const { data } = yield apiService.request(action.payload);
      return data;
    }
    default: return prev;
  }
});

Singleton Hooks – Global State Management

Libraries like Hox provide createModel to generate a global singleton hook that lives for the app’s lifetime.

Limitations of Stream‑like Hooks

Over‑Frequent Changes

React has three frequencies: reconcile, rendering frames, and event dispatch. When events fire faster than rendering frames, additional hacks (e.g., debouncing) are needed.

Avoiding "Flow for Flow's Sake"

While RxJS‑style streams excel at messaging, forcing every piece of logic into a stream can make code verbose; Hooks remain multi‑paradigm and should be used where they add clarity.

Vision

The author plans to release a set of reusable stream‑style hooks called Marble Hooks .

References

RxMarble: https://rxmarbles.com/

ImmutableJS: https://immutable-js.github.io/immutable-js/

Hox: https://github.com/umijs/hox

Marble Hooks: https://github.com/pierrejacques/marble-hooks

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontendState ManagementReacthooksfunctional components
ELab Team
Written by

ELab Team

Sharing fresh technical insights

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.