Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't perform a React state update on an unmounted component. When switching view with react route #59

Closed
hostlund opened this issue Sep 18, 2020 · 2 comments

Comments

@hostlund
Copy link

I'm trying to use PullState in an app with a few react routes and get the error " Can't perform a React state update on an unmounted component." when I use some kind of callback to update the store. Small example inlined. Switch view and press the Test 1, 2 or 3 link. In the console you get (Can't perform a React state update on an unmounted component.) Seems to be "setUpdateTrigger((val) => val + 1);" in useStoreState that triggers the warning. Are we initializing the store the wrong way or should this work?

setUpdateTrigger((val) => val + 1);

store.js

import { Store } from 'pullstate';

export const UIStore = new Store({
  clickTimes: 0
});

Quick example, based on create react app and added react router

import React from "react";
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link
} from "react-router-dom";
import { useStoreState } from 'pullstate';
import { UIStore } from './store';

export default function BasicExample() {
  return (
    <Router>
      <div>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/about">About</Link>
          </li>
          <li>
            <Link to="/dashboard">Dashboard</Link>
          </li>
        </ul>

        <hr />

        <Switch>
          <Route exact path="/">
            <Home />
          </Route>
          <Route path="/about">
            <About />
          </Route>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
        </Switch>
      </div>
    </Router>
  );
}

// You can think of these components as "pages"
// in your app.

function Home() {
    const stateNR = useStoreState(UIStore, (s) => s.clickTimes);
  const updateNumber = () =>  {
    UIStore.update((s) => {
      // eslint-disable-next-line no-param-reassign
      s.clickTimes = s.clickTimes +1
    });
  }
  return (
    <div>
      <h2>Home</h2>
      <a href="#" onClick={(e) => { e.preventDefault(); updateNumber()}}>Test 1</a>
      <br />
      {stateNR}
    </div>
  );
}

function About() {
  const stateNR = useStoreState(UIStore, (s) => s.clickTimes);
  const updateNumber2 = () =>  {
    UIStore.update((s) => {
      // eslint-disable-next-line no-param-reassign
      s.clickTimes = s.clickTimes +1
    });
  }
  return (
    <div>
      <h2>About</h2>
      <a href="#" onClick={(e) => { e.preventDefault(); updateNumber2()}}>Test 2</a>
      <br />
      {stateNR}
    </div>
  );
}

function Dashboard() {
  const stateNR = useStoreState(UIStore, (s) => s.clickTimes);
  const updateNumber3 = () =>  {
    UIStore.update((s) => {
      // eslint-disable-next-line no-param-reassign
      s.clickTimes = s.clickTimes +1
    });
  }
  return (
    <div>
      <h2>Dashboard</h2>
      <a href="#" onClick={(e) => { e.preventDefault(); updateNumber3()}}>Test 3</a>
      <br />
      {stateNR}
    </div>
  );
}
@lostpebble
Copy link
Owner

Hi @hostlund ,

Thanks for bringing this to my attention. I've looked into it, and reproduced what is happening in a Codesandbox here: https://codesandbox.io/s/react-and-pullstate-state-update-leak-cxbky

The strange thing is, in my dev environment I'm not getting these errors at all. Only in the sandbox.


Okay, I just figured out what the difference was between the environments. Its because of <React.StrictMode> wrapping your app. I think I've run into similar issues with strict mode before... It tends to run some things twice which can cause some unintended issues with some libraries. See Apollo as well: apollographql/apollo-client#6209

I don't really know what to do, because as far as I can see the pullstate code is doing everything expected for React components when it comes to mounting and unmounting, and I've never got those warnings about Pullstate popping without StrictMode turned on (but I still get warnings where there obviously are violations of the rule).

You can see the relevant code here:

seEffect(() => {
    updateRef.current.shouldUpdate = true;

    return () => {
      updateRef.current.shouldUpdate = false;
      store._removeUpdateListener(updateRef.current.onStoreUpdate!);
    };
  }, []);

That ref.current.shouldUpdate is a flag that is instantly turned off when the component unmounts, so it should never ever run any state updates after that:

if (updateRef.current.shouldUpdate) {
    updateRef.current.currentSubState = nextSubState;
    setUpdateTrigger((val) => val + 1);
}

It is something to look into in more depth, so I'll keep an eye on it, but I have my suspicions that its a false non-issue caused by StrictMode that we can probably safely ignore it.

I've looked a bit into it now and noticed this issue, which could be a clue: facebook/react#17193
It mentions that useRef state might not be persisting properly in strict mode.

@hostlund
Copy link
Author

Thanks for the clarification @lostpebble

Disabling strict mode was one thing I didn't try. It seems to work without any errors if I remove strict mode so I guess it due to that React issue. I will close this issue now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants