Extracting a Custom Hook

Extraction starts from duplication. Two components each hold a number and bump it by a fixed step; their JSX differs, the lines above it do not. Cut those lines into a function, turn the values that differ into parameters, name it useCounter, and return what the components need. Because the function calls useState the use prefix is mandatory: it is how React 7,897 's linter, and a human reader, know the call has to obey the Rules of Hooks.

A Hook may return anything: an array when the caller will rename the entries (const [value, setValue] = useThing(), the convention useState established), an object when there are more than two. The demo calls useCounter twice; a script clicks the apples button three times and the oranges button twice, 100 ms apart.

One Hook, two independent pieces of stateHTML
<div id="root"></div>
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone@8.0.5/babel.min.js"></script>
<script type="importmap">{ "imports": { "react": "https://esm.sh/react@19.3.0",
  "react-dom/client": "https://esm.sh/react-dom@19.3.0/client" } }</script>
<script type="text/babel" data-type="module">
import React, { useState, useCallback } from 'react';
import { createRoot } from 'react-dom/client';
function useCounter(start, step) {
  const [count, setCount] = useState(start);
  const increment = useCallback(() => setCount(c => c + step), [step]);
  return { count, increment };
}
function Counter({ label, start, step }) {
  const { count, increment } = useCounter(start, step);
  return <p style={{ font: '16px system-ui' }}>{label}: <b>{count}</b>{' '}
    <button onClick={increment}>+{step}</button></p>;
}
createRoot(document.getElementById('root')).render(
  <><Counter label="Apples" start={0} step={1} />
    <Counter label="Oranges" start={100} step={5} /></>);
let t = 150;
for (const i of [0, 0, 0, 1, 1]) {
  setTimeout(() => document.querySelectorAll('button')[i].click(), t);
  t += 100;
}
</script>
Browser output of Listing 2.78
Browser output of 78

Apples reaches 3 and oranges 110: neither call site sees the other's count. A custom Hook shares logic, not state. For one genuinely shared number, put it in a Context provider or an external store and let the Hook read from there — that is what useSyncExternalStore (useSyncExternalStore) exists for.