DEV Community

subbiah chandru
subbiah chandru

Posted on

Solution for problem with useState and eventHandlers

import { useEffect } from "react";
import { useRefState } from "utils";

export default function HomePage() {
  const [mouseDownCounter, setMouseDownCounter] = useRefState(0);

  useEffect(() => {
    window.addEventListener("mousedown", () => {
      setMouseDownCounter(mouseDownCounter + 1);
    });
  }, []);

  return <div>{mouseDownCounter}</div>;
}
Enter fullscreen mode Exit fullscreen mode

With react functional programing we got a problem of accessing latest react state inside addEventListener. “mouseDownCounter“ value will always to 0 inside addEventListener callback function, this can be solved by creating a custom hook that uses ref to store the state. Solution below.

import { useEffect } from "react";
import { useRefState } from "utils";

export default function HomePage() {
  const [
    mouseDownCounter,
    setMouseDownCounter,
    getMouseDownCounter,
  ] = useRefState(0);

  useEffect(() => {
    window.addEventListener("mousedown", () => {
      setMouseDownCounter(getMouseDownCounter() + 1);
    });
  }, []);

  return <div>{mouseDownCounter}</div>;
}
Enter fullscreen mode Exit fullscreen mode

useRefState is same as useState, but has third parameter that exposes the current value of the state. To expose the current state we use react ref. Code below.

/**
 * same as use state, but we get a third param to get current value. This will be useful while working with settimeout, eventHandlers, promises and axios api calls.
 */
export function useRefState<T>(
  defaultValue: T
): [T, (updatedValue: T) => void, () => T] {
  const ref = useRef(defaultValue);
  const [state, setState] = useState(defaultValue);

  function setStateFn(updatedValue: any) {
    ref.current = updatedValue;
    setState(updatedValue);
  }

  function getValueFn() {
    return ref.current;
  }

  return [state, setStateFn, getValueFn];
}
Enter fullscreen mode Exit fullscreen mode

Discussion (4)

Collapse
subbiahc profile image
subbiah chandru Author • Edited on

@michaelsalim

yup that willl work for this specific case. But In case if we want to just get the updated mouseDownCounter and do actions based on the value, useRefState would be a better off solution.

you might think of

let updatedMouseDownCounter = '';
setState(mouseDownCounter => {
uodatedMouseDownCounter = mouseDownCounter;
return mouseDownCounter
}

But this will trigger a re-render. So useRefState is a better option to access updated state value in eventhandlers

Collapse
subbiahc profile image
subbiah chandru Author

@deltd3v

same like a useState, but you get a 3rd parameter, which exposes the current value that could be used inside eventhandlers.

Collapse
michaelsalim profile image
Michael Salim • Edited on

For this specific case, you'll probably be better off using the following:

setMouseDownCounter(prev => prev + 1);
Enter fullscreen mode Exit fullscreen mode

If you pass in a function, it will pass the current value on the parameter

Collapse
deltd3v profile image
deltd3v

Could you please show how you'd use this hook in a component ?