Home > Software design >  window.addEventListener freezing browser with React
window.addEventListener freezing browser with React

Time:07-15

This is my first time with React and I don't understand why this code is like freezing the browser (I'm using Chrome) when clicking multiple times on the window

import "./App.css";
import { useState } from "react";

function App() {
  const [window_touched, setWindow_thouched] = useState(0);
  window.addEventListener("click", () =>
    setWindow_thouched(window_touched   1)
  );

  return (
    <div className="App">
      {`This window has been thouched ${window_touched} times`}
    </div>
  );
}

export default App;

CodePudding user response:

The body of a function component is executed every time the component is rendered. You're constantly adding additional event handlers, never cleaning up the old ones.

Instead, add the handler once when the component is mounted, and remove it when the component is unmounted, using useEffect callback with an empty dependencies array and a cleanup callback:

function App() {
    const [window_touched, set_window_touched] = useState(0);

    useEffect(() => {
        const handler = () => {
            set_window_touched((touched) => touched   1);
        };
        window.addEventListener("click", handler);
        return () => {
            // This is the cleanup function
            window.removeEventListener("click", handler);
        };
    }, []);

    return (
        <div className="App">
            {`This window has been touched ${window_touched} times`}
        </div>
    );
}

(I used the name set_window_touched instead of setWindow_thouched because A) I wanted to fix the typo, and B) To use snake_case consistently. Note, though, that the usual convention would be to use windowTouched and setWindowTouched.)

Note that that uses the callback version of set_window_touched, passing in an update function that receives the up-to-date version of set_window_touched and returns the new value to set. That's because the handler is only created the first time the component is mounted, which means if it used window_touched directly, it would only ever see the value from the first call to App, which would always be 0.

Live Example:

const { useState, useEffect } = React;

function App() {
    const [window_touched, set_window_touched] = useState(0);

    useEffect(() => {
        const handler = () => {
            set_window_touched((touched) => touched   1);
        };
        window.addEventListener("click", handler);
        return () => {
            // This is the cleanup function
            window.removeEventListener("click", handler);
        };
    }, []);

    return (
        <div className="App">
            {`This window has been touched ${window_touched} times`}
        </div>
    );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>


Side note: Instead of that template literal, the more standard way to include text with placeholders in React the majority of the time is to just do it in the JSX:

return (
    <div className="App">
        This window has been touched {window_touched} times
    </div>
);

Live Example:

const { useState, useEffect } = React;

function App() {
    const [window_touched, set_window_touched] = useState(0);

    useEffect(() => {
        const handler = () => {
            set_window_touched((touched) => touched   1);
        };
        window.addEventListener("click", handler);
        return () => {
            // This is the cleanup function
            window.removeEventListener("click", handler);
        };
    }, []);

    return (
        <div className="App">
            This window has been touched {window_touched} times
        </div>
    );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

  • Related