How do I keep App() level state from resetting with react-router-dom in React.js?

Viewed 660

I have a hook that changes the background color of my app. I would like the background color to stay changed when navigating through the site, but the Router resets this state back to its original useState of 'white'. How do I implement this correctly?

Created with create-react-app.

Edited to show minimum reproducible example:

import React, { useState } from 'react';
import { Route, Switch, BrowserRouter } from 'react-router-dom';
import './App.css';

export default function App() {
  const [color, setColor] = useState('white');

  function changeBackground(value) {
    setColor(value);
  };

  return (
    <div className={color}>
      <BrowserRouter>
        <Navbar changeBackground={changeBackground} />
      </BrowserRouter>
    </div>
  );
};

function Navbar(props) {

  return (
    <div>
      <button onClick={() => props.changeBackground('red')}>
        Change Background Color
      </button>
      <div>
        <a href="/page1">Page 1</a>
      </div>
      <div>
        <a href="/page2">Page2</a>
      </div>
      <Switch>
        <Route exact path="/page1"><Page1 /></Route>
        <Route exact path="/page2"><Page2 /></Route>
      </Switch>
    </div>
  );
};

function Page1() {

  return (
    <div>
      Page 1
    </div>
  );
};

function Page2() {

  return (
    <div>
      Page 2
    </div>
  );
};
/* App.css */
.white {
  background-color: white;
}
.red {
  background-color: red;
}

Previous code before adding minimum reproducible example:

import React, { useState } from 'react';
import { Route, Switch, BrowserRouter } from 'react-router-dom';
import './App.css';
import './assets/fonts/fonts.css';
import Navbar from './components/Navbar';
import Design from './components/Design';
import Develop from './components/Develop';
import Else from './components/Else';
import DesignTest from './pages/DesignTest';

function App() {
  const [backgroundColor, setBackgroundColor] = useState('white');


  function changeBackground(value) {
    setBackgroundColor(value);
  };


  return (
    <div className={`w-screen min-h-screen ${backgroundColor}`}>
      {/* <Router> */}
      <Navbar changeBackground={changeBackground} />
      <BrowserRouter>
        <Switch>
          <Route exact path="/"></Route>
          <Route exact path="/design"><Design /></Route>
          <Route exact path="/develop"><Develop /></Route>
          <Route exact path="/else"><Else /></Route>
          <Route exact path="/design/test"><DesignTest /></Route>
        </Switch>
      </BrowserRouter>
    </div>
  );
}

export default App;

function Navbar(props) {

  return (
  <div className="w-screen h-screen inline-grid grid-cols-12 grid-rows-6 tuffyBold tracking-widest">
            <div className="row-start-1 row-end-3 col-start-1 col-end-7 text-right mt-auto mb-auto text-5xl">
              TITLE
         </div>
              <div className="row-start-3 row-end-4 col-start-7 col-end-13 test-left mt-auto mb-auto text-4xl">
                <a href="/design">
                  DESIGN
                </a>
              </div>
              <div className="row-start-4 row-end-5 col-start-7 col-end-13 test-left mt-auto mb-auto text-4xl">
                <a href="/develop">
                  DEVELOP
                </a>
              </div>
              <div className="row-start-5 row-end-7 col-start-7 col-end-13 test-left mt-auto mb-auto text-4xl">
                <a href="/else">
                  EVERY<br />THING<br />ELSE
                </a>
              </div>
            <div className="row-start-1 row-end-3 col-start-7 col-end-13 mt-auto mb-auto ml-auto mr-auto">
              <div className="circle white" onClick={() => props.changeBackground('white')}></div>
            </div>
            <div className="row-start-3 row-end-4 col-start-1 col-end-7 mt-auto mb-auto ml-auto mr-auto">
              <div className="circle yellow" onClick={() => props.changeBackground('yellow')}></div>
            </div>
            <div className="row-start-4 row-end-5 col-start-1 col-end-7 mt-auto mb-auto ml-auto mr-auto">
              <div className="circle blue" onClick={() => props.changeBackground('blue')}></div>
            </div>
            <div className="row-start-5 row-end-6 col-start-1 col-end-7 mt-auto mb-auto ml-auto mr-auto">
              <div className="circle dark" onClick={() => props.changeBackground('dark')}></div>
            </div>
          </div>
          
            );
}

export default Navbar;
.yellow {
  background-color: #CDB900;
  color: black;
}
.blue {
  background-color: #009CCD;
  color: black;
}
.dark {
  background-color: #4B394E;
  color: white;
}
.white {
  background-color: white;
  color: black;
}

4 Answers

I found my issue. I was using <a href="/#"> instead of importing Link from react-router-dom, causing the entire page to re-render. Here is the version that worked as I intended:

import React, { useState } from 'react';
import { Route, Switch, BrowserRouter, Link } from 'react-router-dom';
import './App.css';

export default function App() {
  const [color, setColor] = useState('white');

  function changeBackground(value) {
    setColor(value);
  };

  return (
    <div className={color}>
      <BrowserRouter>
        <Navbar changeBackground={changeBackground} />
      </BrowserRouter>
    </div>
  );
};

function Navbar(props) {

  return (
    <div>
      <button onClick={() => props.changeBackground('red')}>
        Change Background Color
      </button>
      <div>
        <Link to="/page1">Page 1</Link>
      </div>
      <div>
        <Link to="/page2">Page2</Link>
      </div>
      <Switch>
        <Route exact path="/page1"><Page1 /></Route>
        <Route exact path="/page2"><Page2 /></Route>
      </Switch>
    </div>
  );
};

function Page1() {

  return (
    <div>
      Page 1
    </div>
  );
};

function Page2() {

  return (
    <div>
      Page 2
    </div>
  );
};
/* App.css */
.white {
  background-color: white;
}
.red {
  background-color: red;
}

I had the same issue of complete app state refresh as I click on the navbar for a route change. It turned out that the issue was due to not using Link component provided by react router.

In my case I was using bootstrap navbar components. The code that I got from bootstrap site creates navbar link like this:

<Nav.Link href="/home">Home</Nav.Link>

This navigation through 'href' is the problem here. So in order to overcome this issue I wrapped Nav.Link inside LinkContainer. This allowed me to remove href from Nav.Link and I provided the route url using 'to' property in LinkContainer.

<LinkContainer to="/home">
    <Nav.Link>Home</Nav.Link>
</LinkContainer>

In order to use LinkContainer I had to install and import it.

npm install react-router-bootstrap


import { LinkContainer } from "react-router-bootstrap";

Because your Navbar is outside the router component, meaning everything you click something it's actually reloading the page instead of being captured by react-router. Move the navbar component inside browser router:

  return (
    <div className={`w-screen min-h-screen ${backgroundColor}`}>
      {/* <Router> */}

      <BrowserRouter>
        <Navbar changeBackground={changeBackground} />
        <Switch>
          <Route exact path="/"></Route>
          <Route exact path="/design"><Design /></Route>
          <Route exact path="/develop"><Develop /></Route>
          <Route exact path="/else"><Else /></Route>
          <Route exact path="/design/test"><DesignTest /></Route>
        </Switch>
      </BrowserRouter>
    </div>
  );

It is seems that something trigger re-render the whole App. Probably React Developer Tools can help you to find that place in code.

I have created an isolated example - https://codesandbox.io/s/solitary-wood-8oi66?fontsize=14&hidenavigation=1&theme=dark&file=/src/App.js

In this example value with color from the hook also saves into the browser LocalStorage, and use as a default value for the first render, so it makes it possible to keep the background color after reloading the page. Maybe this is what you want to achieve. but I'm not sure.

  const defaultColor = localStorage.getItem("bgColor");
  const [backgroundColor, setBackgroundColor] = useState(
    defaultColor || "white"
  );

  function changeBackground(value) {
    setBackgroundColor(value);
    localStorage.setItem("bgColor", value);
  }

Related