Skip to content

setState hook inside useEffect can cause unavoidable warning Can't perform a React state update #14369

Closed
@istarkov

Description

@istarkov

BUG

What is the current behavior?

Example: https://codesandbox.io/s/6y1x2zr21n clicking on OK button cause Warning: Can't perform a React state update on an unmounted component.

The problem that unsubscribe is called during B event setVisible(v => false); call, see logs:

SET VISIBLE BEFORE 
UNSUBSCRIBE 
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
    in Child (created by Holder)
SET VISIBLE AFTER 

In our case we have this even without RAF call, but on transitionend DOM event.
(It's occurred randomly and rare in our codebase as transitionend event should be called exactly at needed time, but example showed what happens)
Seems like it occurred only if you have a setState call during useEffect callback like setRefresh(v => v + 1); (inside provided code) (after rewriting our codebase to avoid setState calls in useEffect the error has gone)

Code

import React from "react";
import ReactDOM from "react-dom";
import mitt from "mitt";

const emitter = mitt();

const Child = () => {
  const [visible, setVisible] = React.useReducer((s, a) => a, true);
  React.useEffect(() => {
    const handle = () => {
      console.log("SET VISIBLE BEFORE");
      setVisible(v => false); // <--- THIS CALL CAUSES UNSUBSCRIBE AND WARNING ABOUT STATE
      console.log("SET VISIBLE AFTER");
    };
    emitter.on("B", handle);
    return () => {
      console.log("UNSUBSCRIBE");
      emitter.off("B", handle);
    };
  }, []);

  return <div>{visible && <h1>CHILD TEXT</h1>}</div>;
};

const Holder = () => {
  const [refresh, setRefresh] = React.useState(0);
  const visible = React.useRef(true);
  React.useEffect(() => {
    if (refresh === 1) {
      visible.current = false;
      setRefresh(v => v + 1); // <--- This state change from effect caused problems
    }
    const handle = () => {
      setRefresh(v => v + 1);
    };
    emitter.on("A", handle);
    return () => {
      emitter.off("A", handle);
    };
  });

  return <div>{visible.current && <Child />}</div>;
};

function App() {
  return (
    <div>
      <Holder />
      <button
        onClick={() => {
          emitter.emit("A", {});

          requestAnimationFrame(() => {
            emitter.emit("B", {});
          });
        }}
      >
        OK
      </button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

What is the expected behavior?

Do not provide warning if unsubscription is called during "setState" call.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

React 16.7.0-alpha.2

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions