SoFunction
Updated on 2025-04-11

Detailed explanation of how to use addEventListener elegantly in React

existReact HooksWhen using third-party libraries in  , many people will write this (referring to me):

const [count, setCount] = useState(0);
useEffect(() => {
  const library = new Library();
  ("click", () => {
    (count); // Can't get the latest count  });
}, []);

Writing this will cause problems:

It will only bind events when this component is loaded, if other events are usedstate, then when this state changes, the latest one cannot be obtained in the event.state

You will think, I'llstatePut it in dependencies:

const [count, setCount] = useState(0);
useEffect(() => {
  const library = new Library();
  // The click event will be repeatedly bound  ("click", () => {
    (count);
  });
}, [count]);

Doing so will lead to new problems:clickEvents will be repeatedly bound

At this time, you say I'll uninstall it firstclickEvent, in the binding event:

const [count, setCount] = useState(0);
useEffect(() => {
  const library = new Library();
  ("click", handleClick);
  return () => {
    // Events cannot be uninstalled, and they will still be repeatedly bound    handleClick && ("click", handleClick);
  };
}, [count]);

const handleClick = () => {
  (count);
};

You were surprised to find that if you could not uninstall the previous event, you would still repeatedly bind the event.

How to solve this problem?

Events using addEventListener instead of third-party libraries

Used hereaddEventListenerInstant code of events in place of third-party libraries

const Test = (props) => {
  const ref = useRef();
  const [count, setCount] = useState(0);

  useEffect(() => {
    const handleClick = (event) => {
      ("clicked");
      ("count", count);
    };
    const element = ;
    ("click", handleClick);
    return () => {
      ("click", handleClick);
    };
  }, []);

  const onClickIncrement = () => {
    setCount(count + 1);
  };
  return (
    <>
      <h2>Test</h2>
      <button onClick={onClickIncrement}>Click +1</button>
      <div>count: {count}</div>
      <button ref={ref}>Click Test Button</button>
    </>
  );
};

Method 1: state changes, uninstall/bind events

WillstatePut it in dependencies and it needs to be solvedstateThe issue of repeated binding of events during changes

Solve the problem of repeated binding of events, the first thing that comes to mind is event uninstallation

You'll easily think of writing like this

useEffect(() => {
  handleClick && ("click", handleClick);
  ("click", handleClick);
}, [count]);

const handleClick = () => {
  (count);
};

This isReact Hooksis a pit,stateAfter the change,handleClickThe event function will be redeclared, newhandleClickAnd the previous onehandleClickNot an event function, resulting inremoveEventListenerThe removed event function is not the previous event function

Then you will think of me,handleClickAdd oneuseCallback

useEffect(() => {
  handleClick && ("click", handleClick);
  ("click", handleClick);
}, [count]);

const handleClick = useCallback(() => {
  (count);
}, []);

If you write this way, you will still have the same problem: if the dependency is an empty array, you will not get the latest one.state; Put in dependenciesstatestateAfter the change, it is not the same event function, and events cannot be removed.

How to solve this problem?

Save the event function as a state:

  • whencountWhen changing, mount the event and save the event function asstate
  • whenWhen changing, inuseEffect returnThe event function before uninstalling (the closure is used here)

Specific code:

const Test = () => {
  const ref = useRef();
  const [count, setCount] = useState(0);
  const [eventFn, setEventFn] = useState({ fn: null });

  useEffect(() => {
    mountEvent();
  }, [count]);

  const mountEvent = () => {
    if (!) return;
    // && ("click", ); // If you don't understand below, you can also write this way    ("click", handleClick);
    setEventFn({ fn: handleClick });
  };

  useEffect(() => {
    return () => {
       && ("click", ); // Here is a closure, and choose any of the comments above    };
  }, []);

  const handleClick = () => {
    (count);
  };

  const onClickIncrement = () => {
    setCount(count + 1);
  };

  return (
    <>
      <h2>Test</h2>
      <button onClick={onClickIncrement}>Click +1</button>
      <div>count: {count}</div>
      <button ref={ref}>Click Test Button</button>
    </>
  );
};

Method 2: Uninstall events using closures

Using closures, you can simplify the method

const Test = () => {
  const ref = useRef();
  const [count, setCount] = useState(0);

  useEffect(() => {
    const element = ;
    ("click", handleClick);
    return () => {
      ("click", handleClick);
    };
  }, [count]);

  const handleClick = () => {
    (count);
  };

  const onClickIncrement = () => {
    setCount(count + 1);
  };

  return (
    <>
      <h2>Test</h2>
      <button onClick={onClickIncrement}>Click +1</button>
      <div>count: {count}</div>
      <button ref={ref}>Click Test Button</button>
    </>
  );
};

useEffect returnThe variables in the  use closures, which is difficult to understand when I first learned

Method 3: Use ref to save state

refAlthough the saved data cannot be used for page rendering, it can be used asstateBackup, instateUpdated when changesref

You can get the latest in the event functionstateRef

const Test = () => {
  const ref = useRef();
  const [count, setCount] = useState(0);

  const countRef = useRef(count);
  useEffect(() => {
     = count;
  }, [count]);

  useEffect(() => {
    const element = ;
    ("click", handleClick);
  }, []);

  const handleClick = () => {
    ();
  };

  const onClickIncrement = () => {
    setCount(count + 1);
  };

  return (
    <>
      <h2>Test</h2>
      <button onClick={onClickIncrement}>Click +1</button>
      <div>count: {count}</div>
      <button ref={ref}>Click Test Button</button>
    </>
  );
};

Optimize state manual maintenance

There is a problem with the above three methods.stateNeed manual maintenance

How to optimize this step?

Method 1 and method 2, the optimization method is the same: the dependency iscountChange tostate

const [state, setState] = useState({ count: 0 });

useEffect(() => {
  // ...
}, [state]);

Method 3 optimization is to usestateRefSaverefObject, whenstateWhen changing, traversestateGivestateRefAssignment

Used in event functionsstateRef

const [state, setState] = useState({ count: 0 });
const stateRef = useRef({});
useEffect(() => {
  (state).forEach((key) => {
    [key] = state[key];
  });
}, [state]);

This is the end of this article about how to use addEventListener elegantly in React. For more related React addEventListener content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!