Home  >  Q&A  >  body text

Why is useState shared between routes with different props?

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粉571233520P粉571233520174 days ago389

reply all(2)I'll reply

  • P粉496886646

    P粉4968866462024-03-31 18:52:50

    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); // <-- reset to 0 when name prop updates
      }, [name, setCount]);
    
      const onClick = e => {
        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]);
    

    reply
    0
  • P粉608647033

    P粉6086470332024-03-31 18:42:56

    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.

    reply
    0
  • Cancelreply