Why is useState shared between routes with different props?
P粉571233520
P粉571233520 2024-03-30 21:19:56
0
2
439

I have an application with two tabs "Apple" and "Banana". Each tab has a counter implemented using useState.

const Tab = ({ name, children = [] }) => {
  const id = uuid();
  const [ count, setCount ] = useState(0);

  const onClick = e => {
    e.preventDefault();
    setCount(c => c + 1);
  };

  const style = {
    background: "cyan",
    margin: "1em",
  };

  return (
    <section style={style}>
      <h2>{name} Tab</h2>
      <p>Render ID: {id}</p>
      <p>Counter: {count}</p>
      <button onClick={onClick}>+1</button>
      {children}
    </section>
  );
};

What's confusing is that the counter state is shared between the two tabs!

If I increment the counter on one tab and then switch to another tab, the counter also changes.

why is that?


This is my complete application:

import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import { v4 as uuid } from "uuid";
import { HashRouter as Router, Switch, Route, Link } from "react-router-dom";

const Tab = ({ name, children = [] }) => {
  const id = uuid();
  const [ count, setCount ] = useState(0);

  const onClick = e => {
    e.preventDefault();
    setCount(c => c + 1);
  };

  const style = {
    background: "cyan",
    margin: "1em",
  };

  return (
    <section style={style}>
      <h2>{name} Tab</h2>
      <p>Render ID: {id}</p>
      <p>Counter: {count}</p>
      <button onClick={onClick}>+1</button>
      {children}
    </section>
  );
};

const App = () => {
  const id = uuid();

  return (
    <Router>
      <h1>Hello world</h1>
      <p>Render ID: {id}</p>
      <ul>
        <li>
          <Link to="/apple">Apple</Link>
        </li>
        <li>
          <Link to="/banana">Banana</Link>
        </li>
      </ul>
      <Switch>
        <Route
          path="/apple"
          exact={true}
          render={() => {
            return <Tab name="Apple" />;
          }}
        />
        <Route
          path="/banana"
          exact={true}
          render={() => {
            return <Tab name="Banana" />;
          }}
        />
      </Switch>
    </Router>
  );
};

const container = document.getElementById("root");
const root = createRoot(container);

root.render(<App />);

Version:

  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router": "5.2.1",
    "react-router-dom": "5.2.1",
    "uuid": "^9.0.0"
  },

P粉571233520
P粉571233520

reply all(2)
P粉496886646

Adam has a good explanation and answer on what's going on here, it's an optimization that doesn't tear down and reinstall the same React component just because the URL path changes. Using React keys will definitely solve this problem, forcing React to remount the Tab component, thereby "resetting" the count state.

I suggest another approach, when the name attribute changes from "apple" to "banana", keep the routing component mounted and simple to reset the count status and vice versa.

const Tab = ({ name, children = [] }) => {
  const id = uuid();
  const [count, setCount] = useState(0);

  useEffect(() => {
    setCount(0); //  {
    e.preventDefault();
    setCount(c => c + 1);
  };

  const style = {
    background: "cyan",
    margin: "1em",
  };

  return (
    

{name} Tab

Render ID: {id}

Counter: {count}

{children}
); };

This will make RRD optimization work for you, not against you.

If you don't have a passed prop like name to get hints from, you can use location.pathname . Note that this does couple some internal component logic with external details.

Example:

const { pathname } = useLocation();
const [count, setCount] = useState(0);

useEffect(() => {
  setCount(0);
}, [pathname, setCount]);
P粉608647033

This works with Switch in react-router-dom

Ultimately, your component tree remains the same even if you switch routes.

Always Router->Switch->Routing->Tab

Due to the way Switch works, React never "installs" new components, it just reuses the old tree because it can.

I've had this problem before and the solution was to add a key somewhere, like on Tab or Route. I usually add this to Route because it makes more sense to me:


         {
            return ;
          }}
        />
         {
            return ;
          }}
        />
      

Check out this stack blitz:

https://stackblitz.com/edit/react-gj5mcv ?file=src/App.js

Of course, your state will be reset in each tab when each tab is unloaded, which may or may not be ideal. But the solution to this is of course (if this is an issue for you), as usual, to boost status.

Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!