2

I'm developing an React JS application that suppose to run on mobile devices.

Here is the code excerpts from the app:

index.tsx:

import React from 'react';
import ReactDOM from 'react-dom';

import './index.css';
import App from './App';


ReactDOM.render(
    <React.StrictMode>
        <App/>
    </React.StrictMode>,
    document.getElementById('root')
);

App.tsx:

import React from 'react';
import './App.css';

import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';

import {Header} from "./Components/header"
import {Footer} from "./Components/footer"
import {Sidebar} from "./Components/sidebar"
//...

const listener = () => {
    console.log("Main listener")
};

function App() {

    const media = window.matchMedia('(min-width: 700px)');
    media.addEventListener("change", listener);

    return (
        <Router>
            <div className="App">

                <Header />
                <Sidebar />
                <Footer />

                //...

            </div>
        </Router>
    );
}

export default App;

The problem is that the change event handler function (listener) is called TWICE! when I resize the browser window. I've read investigated that the reason for that is the React Strict Mode tag. As the official doc says the Strict Mode makes double-invoking the following functions:

  • Class component constructor, render, and shouldComponentUpdate methods

  • Class component static getDerivedStateFromProps method

  • Function component bodies

  • State updater functions (the first argument to setState)

  • Functions passed to useState, useMemo, or useReducer

My question is WHY the binded event is called TWICE since the doc doesn't list the event handlers as being double invoked!?

It that context WHAT finally makes the change event being invoked twice!?

Daar44
  • 309
  • 5
  • 11
  • Closely related: https://stackoverflow.com/questions/64823462/strictmode-causing-double-event-subscription-in-react – T.J. Crowder Feb 13 '22 at 17:22

2 Answers2

3

This is React's StrictMode proactively bringing a bug to your attention. You should not subscribe to events at the top level of a component function, because that's called every time the component is rendered, so every time you add a new subscription to the event. When you have multiple subscriptions to an event (on different event source objects), you get multiple callbacks to your event callback (one for each subscription).

Instead, subscribe once in a useEffect callback with an empty dependencies array (and unsubscribe from a cleanup callback you return from it), details here.

function App() {

    // An effect only called once on mount (empty deps array)
    useEffect(() => {
        // Subscribe once
        const media = window.matchMedia("(min-width: 700px)");
        media.addEventListener("change", listener);
        // Unsubscribe on unmount
        return () => {
            media.removeEventListener("change", listener);
        };
    }, []);

    // ...
}

In a comment you've asked:

...can You please explain me WHY doesn't the following explicit multiple event subscription:

media.addEventListener("change", listener); 
media.addEventListener("change", listener)

Because in that code, you're calling addEventListener twice on the same MediaQueryList and passing in the same event handler (listener). If you add the same listener for the same event to the same object twice, the second time is ignored (by the DOM event system; different systems handle it differently).

But that's not what your component is doing. Your component function runs more than once, and each time it creates a new MediaQueryList and adds a handler to it, like this:

const listener = () => {
    console.log("listener fired");
};
const YourComponent = () => {
    const media = window.matchMedia("(min-width: 700px)");
    media.addEventListener("change", listener);
};
YourComponent();
YourComponent();

Or to put it even more clearly:

const media1 = window.matchMedia("(min-width: 700px)");
media1.addEventListener("change", listener);
const media2 = window.matchMedia("(min-width: 700px)");
media2.addEventListener("change", listener);

Since they're different MediaQueryList objects, the listener gets added every time (once to each object).

If you run that snippet then change your browser size so the listener fires, you'll see it fires twice.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Ok, You say: _When you have multiple subscriptions to an event, you get multiple callbacks to your event callback (one for each subscription)_ so can You please explain me WHY doesn't the following explicit multiple event subscription: `media.addEventListener("change", listener);media.addEventListener("change", listener);` DOSN'T make the event being called twice!? (I've check it!). – Daar44 Feb 13 '22 at 17:31
  • @Daar44 - I've added an explanation of that (with an example) to the answer. – T.J. Crowder Feb 13 '22 at 17:38
  • Ok friend Y're right. If I just add an extra `App()` call in the _index.tsx_ file (and NOT using the `Strict Mode`) I actually get double output! But to clarify more - You said _Your component function runs more than once_ - is that the _Function component bodies_ point in the official doc I've cited above? – Daar44 Feb 13 '22 at 17:49
  • @Daar44 - Yes, but note that this isn't specific to `StrictMode`. `StrictMode` just helps you by making these issues happen early in dev. The body of a function component is called repeatedly, every time React needs to render the component (like `render` in a class component). React may re-render your component at any time for any reason, although with the `App` component in your question, it's unlikely to (other than `StrictMode`). But the main point here is: Don't subscribe to events in the main body of a function component (just like you wouldn't in the `render` method of a `class`... – T.J. Crowder Feb 13 '22 at 17:55
  • ...component). It's worth having a deep read through the [docs for `useEffect`](https://reactjs.org/docs/hooks-effect.html). – T.J. Crowder Feb 13 '22 at 17:55
0

There's no problem with your code, that is just the default behavior of react when you update your react to react 18. You can disable that by disabling the strict mode, Here's how you can do that: https://upmostly.com/tutorials/why-is-my-useeffect-hook-running-twice-in-react

  • Welcome to Stack Overflow. Link-only answers are not helpful as the website linked may eventually disappear, move or change. Please edit your answer to include all the relevant information in the answer itself. – Marc Sances Dec 20 '22 at 14:06