Uria Levi
Uria Levi

Reputation: 288

How to use stopPropagation() with Next.js?

I am trying to use stopPropagation so my dropdown menu will close after I click on any other elements.

When I try to use stopPropagation & updating a boolean state the button re-render the page (which reset the state back to 'false') than updating the state to true and it stuck on "true" no matter what.

I wrote a code for an example:

import { useEffect, useState } from "react";

export default function IndexPage() {
  const [state, setState] = useState(false);

  useEffect(() => {
    document.body.addEventListener("click", () => {
      setState(false);
    });
  });

  const onButtonClick = (e) => {
    e.stopPropagation();
    setState(!state);
  };

  console.log(state);

  return (
    <div>
      <button onClick={onButtonClick}>Click</button>

      <h1>{state ? "true" : "false"}</h1>
    </div>
  );
}

This problem seems to appear only with Next.js

I wrote the same code with React app and there is no problem.

Next.js codesandbox link

React codesandbox link

Edit:

When you fork the Next.js sandbox project everything is working fine. (and yet it doesn't solve the problem with my original code).

Anyone knows why is it happening?

Upvotes: 7

Views: 5126

Answers (4)

Md Adnan Sami
Md Adnan Sami

Reputation: 1

using event.preventDefault() solved my similar issue

Upvotes: 0

tinky
tinky

Reputation: 1

Thanks, I found a way, nextjs version 13 The fixed code is:

onClick={(event) => { foo(); event.stopPropagation() }}

Upvotes: 0

Lakshya Thakur
Lakshya Thakur

Reputation: 8316

This is a strange one. In the codesandbox container the state is getting preserved for overtime edits and not causing any re-renders as well so probably your e.stopPropagation when added to event handler didn't trigger a re-render and so the old handler for the button is still attached which means that the event listener on the body will keep on triggering as you click the button since our handler never got updated to factor in e.stopPropagation.

When I fork your Next.js setup, everything works fine. Because it's not an incremental change now. The container is rebuilt for me and doesn't preserve any past state and so no stale event handlers or anything.

The best way to test the difference between both setup is to see if the console logs any value when you remove the e.stopPropagation line from both. You will see that Next.js doesn't but the React one does. That means React's state even though preserved still triggered a render and so on new render the event handler that got created knows that :-

Hey, I don't have e.stopPropagation anymore so my event will bubble up

Update - It seems that hitting Save after each change is necessary with Next.js container for a re-render. Don't rely on edits alone like in case of React container.

Upvotes: 3

Uria Levi
Uria Levi

Reputation: 288

I did found a fix for this bug and the first person Who comment me solved this issue.

I add to my code a cleanup for my event listener and it seems to solve the problem.

It seems like cleanup inside useEffect is not just a good practice but a "must".

Because of the cleanup useEffect re-render the page twice and not three times and it prevent it from another render which cause the problem.

Big thanks for anyone tried to help.

The fixed code is:

export default function IndexPage() {
  const [state, setState] = useState(false);

  useEffect(() => {
    window.addEventListener("click", () => setState(false)); 

    return () => window.removeEventListener("click", () => setState(false)); 
  });

  const onButtonClick = (e) => {
    e.stopPropagation();
    setState(!state);
  };

  console.log(state);

  return (
    <div>
      <button onClick={onButtonClick}>Click</button>

      <h1>{state ? "true" : "false"}</h1>
    </div>
  );
}

Next.js sandbox link

Upvotes: 0

Related Questions