Skip to content

Instantly share code, notes, and snippets.

@zetorama
Last active October 30, 2019 19:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zetorama/57b9715e0303967d3fcd18510204b248 to your computer and use it in GitHub Desktop.
Save zetorama/57b9715e0303967d3fcd18510204b248 to your computer and use it in GitHub Desktop.
useCurrent*/useBound* — hooks to bind callbacks to "whatever current value is"
import { useRef, useCallback, useEffect } from 'react'
function useCurrentGetter(value) {
const refValue = useRef(value);
useEffect(() => {
refValue.current = value;
}, [value]);
return useCallback(() => refValue.current, [refValue]);
}
function useCurrentCallback(callback) {
const getCallback = useCurrentGetter(callback);
return useCallback((...args) => getCallback()(...args), [getCallback]);
}
function useBoundCallback(callback, args) {
const currentCallback = useCurrentCallback(callback);
const getArgs = useCurrentGetter(args);
return useCallback(
(...runtimeArgs) => {
const boundArgs = getArgs();
return currentCallback(...boundArgs, ...runtimeArgs);
},
[currentCallback, getArgs]
);
}
function useBoundEffect({ args, effect, deps = [] }) {
const callback = useBoundCallback(effect, args);
return useEffect(callback, [callback, ...deps]);
}
@zetorama
Copy link
Author

zetorama commented Oct 9, 2019

Example, how it simplifies gaearon's useInterval():

function useInterval(callback, delay = 1000) {
  const tick = useCurrentCallback(callback);

  useEffect(() => {
    let id = setInterval(tick, delay);
    return () => clearInterval(id);
  }, [tick, delay]);
}

Example, how it allows bind effects to current values:

function App() {
  const [rates, setRates] = useState(10.0);
  const getRatesDiff = () => Math.random() - 0.5;
  
  // update rates every second
  useInterval(() => setRates(rates + getRatesDiff()));

  // though we want to re-render it only once in a while, regardless of how often rates are updated
  return <SlowlyUpdatedExchangeRates rates={rates.toFixed(4)} />;
}

function SlowlyUpdatedExchangeRates({ holdUpdateSec = 3, rates }) {
  const [tick, setTick] = useState(0);
  const [currentRates, setCurrentRates] = useState(rates);

  // reset current ticks
  useEffect(() => setTick(0), [holdUpdateSec]);

  // increment ticks every second
  useInterval(() => setTick(tick + 1));

  useBoundEffect({
    args: [rates, holdUpdateSec],
    effect: (rates, holdUpdateSec) => {
      if (tick % holdUpdateSec === 0) {
        // trigger re-render
        setCurrentRates(rates);
      }
    },
    deps: [tick]
  });

  return (
    <p>
      <b>Current rates:</b> {currentRates}
    </p>
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment