Home > Software engineering >  Prevent useEffect hook with two dependencies from running twice
Prevent useEffect hook with two dependencies from running twice

Time:12-19

I want to use two properties of a state in the same useEffect hook:

state.keyEvent: keydown from document (to listen to commands like Ctrl R).

state.value: value of input (to perform a text search).

import { useEffect, useReducer } from "react";

const App = () => {
  const initialState = { keyEvent: {}, value: "builder" };
  const [state, updateState] = useReducer(
    (state: any, updates: any) => ({ ...state, ...updates }),
    initialState
  );

  function handleInputChange(event: any) {
    updateState({ value: event.target.value });
  }

  function handleDocumentKeyDown(event: any) {
    updateState({ keyEvent: event });
  }

  useEffect(() => {
    document.addEventListener("keydown", handleDocumentKeyDown);

    return () => {
      document.removeEventListener("keydown", handleDocumentKeyDown);
    };
  }, []);

  useEffect(() => {
    console.log("keyEvent:", state);
  }, [state]);

  return (
    <div>
      <input
        id="input"
        type="text"
        onChange={handleInputChange}
        value={state.value}
      />
    </div>
  );
};

export default App;

This works—except the useEffect hook runs twice when I type in the input element.

I think I have to tell the code: If state.value updates, ignore state.keyEvent and don't run the hook again.

Is that correct? If so, how to accomplish this?

Edit useState same value rerender (forked)

Note: if I put state.keyEvent and state.useEffect in different useEffect hooks, I won't be able to have the latest value of state.value in the hook containing state.keyEvent (because the hook containing state.keyEvent will run first than the hook containing state.value).

CodePudding user response:

If you want to just listen for commands like ctrl r, you can pre-filter them in your keyboard handler and exclude from being pushed to the input:

  ....

  function isCommand(event: KeyboardEvent) {
    return event.ctrlKey && event.key === 'r';
  }

  function handleDocumentKeyDown(event: KeyboardEvent) {
    if (isCommand(event)) {
      event.preventDefault();
      updateState({ keyEvent: event });
    }
  }

CodePudding user response:

keydown event always fires first, so it's enough to check for it

useEffect(() => {
  console.log("keyEvent:", state);
}, [state.keyEvent]);
  • Related