9

I have 2 React JS pages (A & B), when I go from A->B and back to A, page A is refreshed every time. I was under the impression that page is not destroyed. All related questions on StackOverflow seems to be about the opposite problem.

The reason the page refreshes is because useEffect() is called when the back button is pressed despite using useState() to prevent this. I even tried replacing 'refresh' with a 'props.id' parameter (that never changes). See code below:

Here's my code to page A:

import { useHistory, useParams } from "react-router-dom";
import React, { useState, useEffect, useRef } from "react";
import { Link } from "react-router-dom";

export default function Test(props) {
    const [refresh, setRefresh] = useState(false);
    
    useEffect(() => {
        console.log("useEffect called: "+refresh);
        setRefresh(true);
    },[refresh]);

    return (
        <>
            Hello from Test
            <Link to="/test2">Test me</Link>
        </>
    );
}

I'm using react-router-dom: "^5.1.2", and import { BrowserRouter as Router } from "react-router-dom"; in App.js and specified:

  <Switch>
      <Route exact path="/">
        <Home />
      </Route>
          <Route exact path="/test">
        <Test id="1"/>
      </Route>
      <Route exact path="/test2">
        <Test2 />
      </Route>
      .....

Does anyone know how to prevent useEffect() from being triggered when returning to page? The actual page A fetches using a REST call and display a long list of items and I do not want the page to refresh every time the user load page B to view item and then returns to the page.

3 Answers 3

3

You need to add a condition to useEffect. If you only want to setRefresh to true if its false, then do something like:

useEffect(() => {
  if(!refresh) setRefresh(true)
}, [refresh])

Since you are starting with const [refresh, setRefresh] = useState(false) and are not changing refresh anywhere else in the component, this will run once everytime the component loads (not renders).

If you want to run this once in the lifetime of the app and not the component, you need to persist the information outside the component, by either lifting the state up to a parent component and persisting the information is something like localstorage/sessionstorage.

You could then extract this information whenever your component loads and set the refresh state variable accordingly.

Let's say you just want to setRefresh to true once. Add this useEffect:

useEffect(() => {
  let persistedRefresh
  try {
    persistedRefresh = !!JSON.parse(window.localstorage.getItem('THE_KEY_TO_REFRESH_VALUE'))
  } catch(error) {
    persistedRefresh = false  
  }
  setRefresh(persistedRefresh)
}, [])

This useEffect will run whenever the component loads, and update the state variable, triggering the previous useEffect.

We also need to modify the previous useEffect:

useEffect(() => {
  if(!refresh) { 
    setRefresh(true)
    window.localstorage.setItem('THE_KEY_TO_REFRESH_VALUE', JSON.stringify(true))
  }
}, [refresh])

In this useEffect we are updating the persisted value so that whenever the component loads,

  • it will check the persisted value,
  • refresh if needed, and
  • update the persisted value for the next loads.

This is how you do it without any extra dependencies.

2

I can see that you're importing the very useful useHistory prop, but not doing much with it. It can actually be used to check if a user is navigating to the page by using the back button. useHistory()'s action properly will tell you everything you need. If the back button was used, action will be "POP". So you can put some logic into your useEffect to check for that:

  const history = useHistory();
  React.useEffect(() => {
    if (history.action === "POP")
      console.log("Back button used. Not running stuff");
    else console.log("useEffect called in home");
  }, []);

Here is a Sanbox. And here you can actually test the sandbox code in a dedicate browser window: https://okqj3.csb.app/

Click the "About" link and then use the back button to go back to "Home", in the console you will see how the Home element's useEffect function catches it.

4
  • Thanks for your answer. I've actually tried this and noticed that history.action is always equal to 'POP', even when I load the page the first time. Which could explain why I'm encountering this problem. I noticed a warning in the console: "<BrowserRouter> ignores the history prop. To use a custom history, use import { Router } instead of import { BrowserRouter as Router }." Could this be the problem?
    – jvence
    Commented Jan 25, 2021 at 10:10
  • When I test your sandbox okqj3.csb.app I get the same effect whereby the first time the page is loaded, it shows 'Back button used. Not running stuff;' (both in Chrome and Safari in incognito mode)
    – jvence
    Commented Jan 25, 2021 at 10:32
  • @jvence you are correct. It is also POP on first load. It turns to PUSH when you go back to it from the other page, but yes, there should be something to catch it when you first load the page. I will check it out in a little bit. Thanks for the heads up.
    – codemonkey
    Commented Jan 25, 2021 at 18:23
  • it doesn't turn to push unless you use the Link to go to Page 1, if you use the back button on the browser, it's still a POP.
    – jvence
    Commented Jan 25, 2021 at 20:07
1

Solution 1 (Correct way)

Use Stateless components and have a common super state (Redux will be of great assistance), and bind you page/data to common state so even if the state changes, the page will always render the current state creating an illusion of page retaining the state (I used it to run large queries and store progress/result in redux so even if I open another page and come back then also I see query in progress or result).

However I am not really sure what your use case is.

Solution 2 (slightly wrong way)

Use React.memo,You can use it when you don't want to update a component that you think is static

For function Components:

 const Mycomponents = React.memo(props => {   
      return <div> 
                 No updates on this component when rendering,  use useEffect to verify too 
            </div>;
 });

You shouldn't be defining any method/functionality/dynamic calculation inside this kind of method just to avoid getting irregular data

Not the answer you're looking for? Browse other questions tagged or ask your own question.