Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does react trigger componentDidMount with safari cache?

React 16 triggers componentDidMount() when going back in Safari, even tho the component never unmounted. How does react know when to mount?

class Foo extends React.Component {
  state = {
    loading: false
  }

  componentDidMount() {
    // when going back in safari
    // triggers in react 16, but not in 15.3 or preact
    console.log('mounted');
  }

  componentWillUnmount() {
    // will never trigger
    console.log('will unmount');
  }

  leave() {
    this.setState({
      loading: true
    });
    setTimeout(() => {
      window.location.href = 'https://github.com/';
    }, 2000);
  }

  render() {
    return this.state.loading ? <div>loading...</div> : <button onClick={this.leave.bind(this)}>leave</button>;
  }
}

Background

Safari uses bfcache. If you go back it takes the last page from cache.

When using react 15.3 or libraries such as preact, leaving the page will not trigger componentWillUnmount and going back will not trigger componentDidMount.

This behaviour causes several issues - for example when you set your page state to loading before redirecting. If the user goes back, the state is still set to loading and you cannot even reset the state using componentDidMount, because it never triggers.

There is a solution, by using onpageshow, but since it only triggers one time, you have to reload the whole page using window.location.reload(). This is also the reason react cannot rely on this solution.

like image 495
oshell Avatar asked May 22 '19 14:05

oshell


People also ask

What triggers componentDidMount?

Render JavaScript with Initial Render The componentDidMount() method will be triggered as soon as the component is mounted or inserted into the DOM. The basic syntax to use componentDidMount() is looks like this. This method used widely by developers because it loads immediately once the component is mounted.

Why does componentDidMount run twice?

Adding a key prop to your React component may trigger componentDidMount to be called multiple times.

How many times does componentDidMount run?

2. How many times componentDidMount is called? React components call componentDidMount only once by default. You can run the component multiple times if you delete the component or change the props or state.

Is componentDidMount called after setState?

componentDidMount gets executed only once, when the React component is mounted to the tree, that's why it is not called after setState . So you need componentDidUpdate, this callback gets executed on every rerender except for the initial one. For the initial one you may want to use componentDidMount.


1 Answers

I do not know exactly how React 16 is calling the mount, but it is a completely different engine, so It may be on purpose or not. One thing you can do to work around the issue is to schedule a state reset just before redirecting, like this:

<html>
  <head>
    <script
      crossorigin
      src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react.js"
    ></script>
    <script
      crossorigin
      src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react-dom.js"
    ></script>
    <script src="https://unpkg.com/[email protected]/babel.min.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script type="text/babel">
      class Foo extends React.Component {
        state = {
          loading: false
        };
        componentDidMount() {
          console.log("mounted");
        }
        leave() {
          this.setState({
            loading: true
          });
          setTimeout(() => {
            this.setupReset();
            window.location.href = "https://github.com";
          }, 2000);
        }

        setupReset() {
          let interval = setInterval(() => {
            if (
              !!window.performance &&
              window.performance.navigation.type === 2
            ) {
              clearInterval(interval);
              console.log('reseting');
              this.setState({ loading: false });
            }
          },500);
        }

        render() {
          return this.state.loading ? (
            <div>loading...</div>
          ) : (
            <button onClick={this.leave.bind(this)}>leave</button>
          );
        }
      }
      ReactDOM.render(<Foo />, document.getElementById("app"));
    </script>
  </body>
</html>

and then when you go back the execution resumes and it is possible to detect if its coming from history and reset the state.

you could actually setup this reset mechanism right on componentDidMount, the first time.

like image 81
Tiago Coelho Avatar answered Oct 31 '22 14:10

Tiago Coelho