Skip to content

Instantly share code, notes, and snippets.

@sag1v
Created August 28, 2020 11:55
Show Gist options
  • Select an option

  • Save sag1v/c553d1a0f646255ed650c79c7908422e to your computer and use it in GitHub Desktop.

Select an option

Save sag1v/c553d1a0f646255ed650c79c7908422e to your computer and use it in GitHub Desktop.

Revisions

  1. sag1v created this gist Aug 28, 2020.
    42 changes: 42 additions & 0 deletions useStateWithCallback.jsx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,42 @@
    function useStateWithCallback(initialValue) {
    const [state, setState] = useState(initialValue);

    // we need to track down last changes to support synchronous updates
    // e.g, addEventListener handlers
    const lastStateRef = useRef(initialValue);

    // we need this flag for 2 reasons:
    // 1. To prevent the call on mount (first useEffect call)
    // 2. To force the effect to run when the state wasn't really updated
    // i.e next-state === previous-state.
    const [shouldRunCBs, setRunCBs] = useState(false);

    // tracking a queue because we may have more than 1 callback per update?
    const cbQRef = useRef([]);

    function customSetState(value, cb) {
    if (typeof cb === "function") {
    cbQRef.current.push(cb);
    // we force the effect to run even if the state wasn't really updated
    // i.e next-state === previous-state.
    // this is how the callback in classes work as well
    // we can opt-out from this behaviour though
    setRunCBs(true);
    }
    setState(value);
    }

    useEffect(() => {
    if (shouldRunCBs && state !== lastStateRef.current) {
    // we must pass back the new value
    //because the consumers can't get it via the closure of thier component
    // and they don't have an instance like in classes.
    cbQRef.current.forEach(cb => cb(state));
    cbQRef.current = [];
    setRunCBs(false);
    lastStateRef.current = state;
    }
    }, [state, shouldRunCBs]);

    return [state, useCallback(customSetState, [])];
    }