componentWillUnmount() не вызывается при обновлении текущей страницы

У меня возникла эта проблема, когда мой код в методе componentDidMount() не срабатывал должным образом при обновлении текущей страницы (а затем и компонента). Тем не менее, он отлично работает, просто просматривая и прокладывая маршрут через мой сайт, нажав ссылки. Обновить текущую страницу? Не случайно.

Я выяснил, что проблема заключается в том, что componentWillUnmount() не запускается, когда я обновляю страницу и запускаю мелкие клики и перемещаю свой веб-сайт/приложение.

Запуск componentWillUnmount() имеет решающее значение для моего приложения, поскольку данные, которые я загружаю и обрабатываю в методе componentDidMount(), очень важны при отображении информации пользователям.

Мне нужно, чтобы componentWillUnmount() вызывался при обновлении страницы, потому что в моей функции componentWillMount() (которую нужно повторно отобразить после каждого обновления) я делаю некоторую простую фильтрацию и сохраняю эту переменную в значении состояния, которое необходимо чтобы присутствовать в переменной состояния logos, чтобы остальная часть компонента работала. Это не изменяет или не принимает новые значения в любое время в течение жизненного цикла компонента.

componentWillMount(){
  if(dataReady.get(true)){
    let logos = this.props.questions[0].data.logos.length > 0 ? this.props.questions[0].data.logos.filter((item) => {
      if(item.logo === true && item.location !== ""){
        return item;
      }
    }) : [];
    this.setState({logos: logos});
  }
};

Cliffs:

  • Я выполняю фильтрацию БД методом componentWillMount()
  • Нужно, чтобы он присутствовал в компоненте после обновления
  • Но у меня есть проблема, когда componentWillUnmount() не запускается, когда страница обновляется
  • Нужна помощь
  • Пожалуйста,

Ответ 1

Когда страница обновляется, реакция не имеет возможности размонтировать компоненты как обычно. Используйте событие window.onbeforeunload чтобы установить обработчик для обновления (прочитайте комментарии в коде):

class Demo extends React.Component {
    constructor(props, context) {
        super(props, context);

        this.componentCleanup = this.componentCleanup.bind(this);
    }

    componentCleanup() { // this will hold the cleanup code
        // whatever you want to do when the component is unmounted or page refreshes
    }

    componentWillMount(){
      if(dataReady.get(true)){
        let logos = this.props.questions[0].data.logos.length > 0 ? this.props.questions[0].data.logos.filter((item) => {
          if(item.logo === true && item.location !== ""){
            return item;
          }
        }) : [];

        this.setState({ logos });
      }
    }

    componentDidMount(){
      window.addEventListener('beforeunload', this.componentCleanup);
    }

    componentWillUnmount() {
        this.componentCleanup();
        window.removeEventListener('beforeunload', this.componentCleanup); // remove the event handler for normal unmounting
    }

}

useWindowUnloadEffect Hook

Я извлек код для многоразового хука на основе useEffect:

// The hook

const { useEffect, useRef, useState } = React

const addHandler = handler => window.addEventListener('beforeunload', handler)
const removeHandler = handler => window.removeEventListener('beforeunload', handler)

const useWindowUnloadEffect = (handler, callOnCleanup) => {
  const prevHandler = useRef(null)
  
  useEffect(() => {
    if(prevHandler.current) removeHandler(oldHandler.current) // remove the the current event listener, if one exists
    
    prevHandler.current = handler
    
    addHandler(handler)
    
    return () => {
      if(callOnCleanup) handler()
    
      removeHandler(handler)
    }
  }, [handler])
}


// Usage example
  
const Child = () => {
  useWindowUnloadEffect(() => console.log('unloaded'), true)
  
  return <div>example</div>
}

const Demo = () => {
  const [show, changeShow] = useState(true)

  return (
    <div>
      <button onClick={() => changeShow(!show)}>{show ? 'hide' : 'show'}</button>
      {show ? <Child /> : null}
    </div>
  )
}

ReactDOM.render(
  <Demo />,
  root
)
<script crossorigin src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="root"></div>

Ответ 2

Я также столкнулся с этой проблемой и понял, что мне нужно убедиться, что по крайней мере 2 компонента всегда будут изящно размонтироваться. Поэтому я, наконец, создал компонент высокого порядка, который гарантирует, что упакованный компонент всегда размонтирован

import React, {Component} from 'react'

// this high order component will ensure that the Wrapped Component
// will always be unmounted, even if React does not have the time to
// call componentWillUnmount function
export default function withGracefulUnmount(WrappedComponent) {

    return class extends Component {

        constructor(props){
            super(props);
            this.state = { mounted: false };
            this.componentGracefulUnmount = this.componentGracefulUnmount.bind(this)
        }


        componentGracefulUnmount(){
            this.setState({mounted: false});

            window.removeEventListener('beforeunload', this.componentGracefulUnmount);
        }

        componentWillMount(){
            this.setState({mounted: true})
        }

        componentDidMount(){
            // make sure the componentWillUnmount of the wrapped instance is executed even if React
            // does not have the time to unmount properly. we achieve that by
            // * hooking on beforeunload for normal page browsing
            // * hooking on turbolinks:before-render for turbolinks page browsing
            window.addEventListener('beforeunload', this.componentGracefulUnmount);
        }

        componentWillUnmount(){
            this.componentGracefulUnmount()
        }

        render(){

            let { mounted }  = this.state;

            if (mounted) {
                return <WrappedComponent {...this.props} />
            } else {
                return null // force the unmount
            }
        }
    }

}

Ответ 3

Я вижу, что этот вопрос имеет более тысячи просмотров, поэтому я объясню, как я решил эту проблему:

Чтобы решить эту конкретную проблему, наиболее разумным способом является создание компонента верхнего уровня, который загружает вашу подписку или базу данных, чтобы вы загружали необходимые данные, прежде чем передавать их вашему дочернему компоненту, что полностью устранит необходимость использования componentWillMount(). Кроме того, вы можете выполнять вычисления в компоненте верхнего уровня и просто передавать их в качестве реквизита для использования в принимающем компоненте

Например:

class UpperLevelComponent extends React.Component {

render() {
if(this.props.isReady) {
return(<ChildComponent {...props}/>)
}
}
}

export default createContainer(() => {
const data = Meteor.subscribe("myData");
const isReady = data.ready();

return {
isReady,
data: MyData.find.fetch()
}
})

В приведенном выше примере я использую реактивный контейнер Meteor для получения моих данных MongoDB и жду, пока он полностью завершит подписку, прежде чем я отрисую дочерний компонент, передав ему любые реквизиты, которые я хочу. Если вы загружаете все свои данные в компонент более высокого уровня, вам не придется полагаться на метод componentWillMount() для запуска после каждого обновления. Данные будут готовы в компоненте верхнего уровня, поэтому вы можете использовать его, как вы хотите, в дочернем компоненте.