This is a follow-up to useCounter. In this version, the counter behavior stays the same, but the returned methods should be memoized; the same function instance is returned across re-renders when its dependencies have not changed.
export default function Component() {const { count, increment, decrement, reset, setCount } = useCounter();return (<div><p>Counter: {count}</p><button onClick={increment}>Increment</button><button onClick={decrement}>Decrement</button><button onClick={reset}>Reset</button></div>);}
initialValue: number: Initial value of the counter state. If not provided, it should default to 0.The useCounter hook returns an object with the following properties:
count: number: The current counter valueincrement: () => void: A function to increment the counter valuedecrement: () => void: A function to decrement the counter valuereset: () => void: A function to reset the counter value to initialValue, or 0 if not providedsetCount: (value: number) => void: A function to set the counter value to value; it has the same signature as setStateincrement, decrement, and setCount must be the same function instance across re-renders. reset should also stay stable as long as initialValue has not changed.
Part II preserves the same counter behavior as useCounter, but adds a stronger API guarantee: the helper methods should keep the same function identity across re-renders whenever their behavior has not changed.
useCounter now has two layers:
useState owns the current count.useCallback controls the identity of the helper functions returned to callers.The state shape is the same as the first useCounter: initialize from initialValue, then update the count through setCount. The extra requirement only changes how the helper functions are defined.
Dependencies are the main change:
| Returned function | Reads latest count? | Dependencies | Identity changes when |
|---|---|---|---|
increment | Via functional updater | [] | Never during the hook lifetime |
decrement | Via functional updater | [] | Never during the hook lifetime |
reset | No, writes initialValue | [initialValue] | initialValue changes |
setCount | React owns it | N/A | React keeps it stable |
increment and decrement should use functional state updates:
const increment = useCallback(() => {setCount((count) => count + 1);}, []);
Because the next value is computed from React's latest state, the callback does not need to close over count. That keeps the dependency list empty and the function reference stable across count changes.
reset is different because it writes the current initialValue argument:
const reset = useCallback(() => {setCount(initialValue);}, [initialValue]);
Its identity should stay stable while initialValue is the same, and change when initialValue changes. setCount can be returned directly because React state setters already have stable identity and support both direct values and updater functions.
For a sequence starting at 5, setCount((x) => x + 2), increment(), decrement(), and reset() produces 7, 8, 7, and then 5. The helper identities stay stable through the count changes because the count itself is never read from their closures.
import { useCallback, useState } from 'react';/*** @param number initialValue* @return Object*/export default function useCounter(initialValue = 0) {const [count, setCount] = useState(initialValue);const increment = useCallback(() => {setCount((x) => x + 1);}, []);const decrement = useCallback(() => {setCount((x) => x - 1);}, []);const reset = useCallback(() => {setCount(initialValue);}, [initialValue]);return {count,increment,decrement,reset,setCount,};}
Closing over count: Writing setCount(count + 1) makes increment depend on count, so the callback has to change identity whenever the counter changes. Use the functional updater form instead.
Omitting initialValue from reset: reset reads initialValue, so it belongs in the dependency list. Leaving it out can reset to an old value after the caller rerenders the hook with a different argument.
Replacing setCount with a narrower wrapper: The returned setCount should behave like React's own setter. Returning the original setter preserves updater calls such as setCount((count) => count + 2) and keeps the reference stable.
initialValue after mount does not automatically change count; it changes what reset will write.increment, decrement, reset, and setCount.console.log() statements will appear here.