setState (…): Може оновити лише змонтований або монтажний компонент. Зазвичай це означає, що ви викликали setState () для немонтованого компонента. Це не-операція


77
componentDidMount(prevProps, prevState, prevContext) {
    let [audioNode, songLen] = [this.refs.audio, List.length-1];

    audioNode.addEventListener('ended', () => {
        this._endedPlay(songLen, () => {
            this._currSong(this.state.songIndex);
            this._Play(audioNode);
        });
    });

    audioNode.addEventListener('timeupdate', () => {
        let [remainTime, remainTimeMin, remainTimeSec, remainTimeInfo] = [];

        if(!isNaN(audioNode.duration)) {
            remainTime = audioNode.duration - audioNode.currentTime;
            remainTimeMin = parseInt(remainTime/60);  // 剩余分
            remainTimeSec = parseInt(remainTime%60);  // 剩余秒

            if(remainTimeSec < 10) {
                remainTimeSec = '0'+remainTimeSec;
            }
            remainTimeInfo = remainTimeMin + ':' + remainTimeSec;
            this.setState({'time': remainTimeInfo});
        }
    });
}

componentWillUnmount () {
    let audio = this.refs.audio;
    audio.removeEventListener('timeupdate');
    audio.removeEventListener('ended');
}

Помилка:

Попередження: setState (...): Може оновлювати лише змонтований або монтажний компонент. Зазвичай це означає, що ви викликали setState () для немонтованого компонента. Це не-операція. Перевірте код для невизначеного компонента.

Я видаляюEventListener "закінчився" в componentWillUnmount, але він не працює. тому що я додаю this.setState({'time': remainTimeInfo});в componentDidMount.


Я думаю, це дуже схоже на stackoverflow.com/questions/47923656/…
Едуард Яцко

Відповіді:


98

Я вирішив це шляхом присвоєння реф до компоненту , а потім перевірка , якщо арбітр існує до установки стану:

myMethod(){
  if (this.refs.myRef) 
   this.setState({myVar: true});
}

render() {
  return (
    <div ref="myRef">
      {this.state.myVar}
    </div>
  );
}

І останнім часом, оскільки я використовую переважно функціональні компоненти, я використовую такий шаблон:

const Component = () => {
  const ref = React.useRef(null);
  const [count, setCount] = React.useState(0);

  const increment = () => {
    setTimeout(() => { // usually fetching API data here
      if (ref.current !== null) {
        setCount((count) => count + 1);
      }
    }, 100);
  };

  return (
    <button onClick={increment} ref={ref}>
      Async Increment {count}
    </button>
  );
};

Дуже елегантне рішення!
tenko

11
Дякую за це! Однак це дуже обтяжує ситуацію. Практично всі мої сцени виконують завантаження на основі обіцянок і видають цю помилку. Чому React не може просто мовчки відмовити запускати setState у відключеному стані. Це не так, як би хтось побачив різницю.
mienaikoe

Фантастичне рішення !! Такий простий і такий ефективний!
Бруд

Це теж застаріло. Але це солодке і просте рішення!
Прамеш Байрачар'я,

Чому не можна реагувати, дозволяючи встановлювати стан, оскільки це не матиме значення.
PatS

23

removeEventListenerмає той самий підпис, що і addEventListener. Усі аргументи повинні бути абсолютно однаковими, щоб видалити слухача.

var onEnded = () => {};
audioNode.addEventListener('ended', onEnded, false);

this.cleanup = () => {
  audioNode.removeEventListener('ended', onEnded, false);
}

А у дзвінку componentWillUnmount this.cleanup().


4

Я зіткнувся з цією проблемою, тому що використовував setStateзамість stateконструктора.

ПРИКЛАД

Змініть такий неправильний код

constructor(props) {
  super(props);
  this.setState({
    key: ''
  });
}

до

constructor(props) {
  super(props);
  this.state = {
    key: ''
  }; 
}

блін, я насправді зробив це з випадком щойно. спасибі
user1189352

3

Редагувати : isMountedзастаріло і, можливо, буде видалено в пізніших версіях React. Дивіться це і це, isMount - це Антипаттерн .


Як зазначається в попередженні, ви викликаєте this.setStateкомпонент, який був змонтований, але з тих пір відключений.

Щоб переконатися, що ваш код у безпеці, ви можете обернути його

if (this.isMounted()) {
    this.setState({'time': remainTimeInfo});
}

щоб компонент все ще був змонтований.


4
this.isMount застаріло, і це не вирішує випадки витоку слухачів подій (що здебільшого тому не підтримується)
Brigand

Дякуємо, що вказали на це, але чому він все ще використовується, тобто завантажує початкові дані через AJAX ? Де я можу побачити його поточний статус?
Даніель Б

isMount - це Антипаттерн - facebook.github.io/react/blog/2015/12/16/…
Колбі

@Kolby, так, моя відповідь це згадує.
Даніель Б

3

Я зіткнувся з такою ж проблемою, оскільки оновив останню версію для реагування. Вирішено як нижче.

Мій код був

async componentDidMount() {
  const { default: Component } = await importComponent();
  Nprogress.done();
  this.setState({
    component: <Component {...this.props} />
  });
}

Змінено на

componentWillUnmount() {
  this.mounted = false;
}
async componentDidMount() {
  this.mounted = true;
  const { default: Component } = await importComponent();
  if (this.mounted) {
    this.setState({
      component: <Component {...this.props} />
    });
  }
}

2

Я мав цю проблему раніше, і вирішив її згідно з офіційною сторінкою React isMount is Antipattern .

Встановіть isMountedпрапорець властивості, який має значення true componentDidMount, і перемкніть його false componentWillUnmount. Коли ви будете setState()у зворотних викликах, isMountedспочатку перевірте ! Це працює для мене.

state = {
    isMounted: false
  }
  componentDidMount() {
      this.setState({isMounted: true})
  }
  componentWillUnmount(){
      this.setState({isMounted: false})
  }

зворотний дзвінок:

if (this.state.isMounted) {
 this.setState({'time': remainTimeInfo});}

10
Це погано. Немає причин економити isMountedна державі. Ви повинні використовувати просто this.isMounted. Це не має нічого спільного із станом реакції.
Султан

1
Крім того, ви не можете викликати this.setState на "componentWillUnmount": learn.co/lessons/react-component-mounting-and-unmounting
Marco Godínez

1
Можливо, навіть this._isMountedтак, це не суперечить або не викликає попереджень від застарілих isMounted(), згідно із викликаною офіційною документацією React.
Цезар Д.

Насправді, очікуваний код слід скасувати в компонентіWillUnmount. Це все той же
антипаттерн

1

Багато відповідей у ​​цій темі мають сенс використання посилань, але я думаю, що повний приклад був би непоганим. Оскільки ви оперуєте фактичним вузлом DOM, використовуючи прослуховувач подій і виходячи з контексту React, посилання слід вважати стандартним рішенням. Ось повний приклад:

class someComponent extends Component {
  constructor(props) {
    super(props)
    this.node = null
  }
  render() {
    return (
      <div ref={node => { this.node = node }}>Content</div>
    )
  }
  handleEvent(event) {
    if (this.node) {
      this.setState({...})
    }
  }
  componentDidMount() {
    //as soon as render completes, the node will be registered.
    const handleEvent = this.handleEvent.bind(this)
    this.node.addEventListener('click', handleEvent)
  }
  componentWillUnmount() {
    const handleEvent = this.handleEvent.bind(this)
    this.node.removeEventListener('click', handleEvent)
  }
}

1

addEventListener і removeEventListener , Зворотний дзвінок не повинен бути анонімним внутрішнім класом, і вони повинні мати однакові параметри


2
як сказав @FakeRainBrigand, оскільки у мене немає репутації, щоб коментувати, я хочу сказати: "Зворотний дзвінок не повинен бути анонімним внутрішнім класом"
Mingo

Дякуємо за вказівку на те, що зворотний дзвінок не повинен бути анонімною функцією
Васим Абузахер

0

Я отримував це попередження, коли хотів показати спливаюче вікно (модальний спосіб завантаження) про зворотний виклик успіху / відмови запиту Ajax. Крім того, setState не працював, і мій спливаючий модальний не відображався.

Нижче була моя ситуація-

<Component /> (Containing my Ajax function)
    <ChildComponent />
        <GrandChildComponent /> (Containing my PopupModal, onSuccess callback)

Я викликав функцію ajax компонента з компонента онука, передаючи зворотний виклик onSuccess (визначений у компоненті онука), який встановлював стан для відображення спливаючого модаля.

Я змінив його на -

<Component /> (Containing my Ajax function, PopupModal)
    <ChildComponent />
        <GrandChildComponent /> 

Натомість я зателефонував setState (onSuccess Callback), щоб показати спливаючий модальний компонент у самому компоненті (ajax callback) та вирішити проблему.

У другому випадку: компонент відображався двічі (я двічі включав bundle.js у html).


0

Назвівши метод замість анонімної функції у audioNode.addEventListenerзворотному виклику, слід виключити тематичне попередження:

    componentDidMount(prevProps, prevState, prevContext) {
    let [audioNode, songLen] = [this.refs.audio, List.length-1];

    audioNode.addEventListener('ended', () => {
        this._endedPlay(songLen, () => {
            this._currSong(this.state.songIndex);
            this._Play(audioNode);
        });
    });

    audioNode.addEventListener('timeupdate', this.callbackMethod );
}

callBackMethod = () => {
    let [remainTime, remainTimeMin, remainTimeSec, remainTimeInfo] = [];

    if(!isNaN(audioNode.duration)) {
        remainTime = audioNode.duration - audioNode.currentTime;
        remainTimeMin = parseInt(remainTime/60);  // 剩余分
        remainTimeSec = parseInt(remainTime%60);  // 剩余秒

        if(remainTimeSec < 10) {
            remainTimeSec = '0'+remainTimeSec;
        }
        remainTimeInfo = remainTimeMin + ':' + remainTimeSec;
        this.setState({'time': remainTimeInfo});
    }
}

І так, іменований метод потрібен у будь-якому випадку, оскільки removeEventListenerне працюватиме з анонімними зворотними викликами, як уже згадувалося вище кілька разів.


0
  1. Скасувати всі операції асинхронізації в componentWillUnmount
  2. Компонент перевірки вже демонтується під час асинхронного виклику setState,
    оскільки прапор isMount застарілий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.