setState () всередині компонентаDidUpdate ()


131

Я пишу сценарій, який переміщує спадне меню нижче або вище, залежно від висоти випадаючого місця та положення входу на екрані. Також я хочу встановити модифікатор до випадаючого відповідно до його напряму. Але використання setStateвнутрішньої частини componentDidUpdateстворює нескінченну петлю (що очевидно)

Я знайшов рішення у використанні getDOMNodeта встановленні назви класу безпосередньо у спадному меню, але відчуваю, що має бути краще рішення за допомогою інструментів React. Хтось може мені допомогти?

Ось частина робочого коду з getDOMNode(тобто трохи занедбаною логікою позиціонування для спрощення коду)

let SearchDropdown = React.createClass({
    componentDidUpdate(params) {
        let el = this.getDOMNode();
        el.classList.remove('dropDown-top');
        if(needToMoveOnTop(el)) {
            el.top = newTopValue;
            el.right = newRightValue;
            el.classList.add('dropDown-top');
        }
    },
    render() {
        let dataFeed = this.props.dataFeed;
        return (
            <DropDown >
                {dataFeed.map((data, i) => {
                    return (<DropDownRow key={response.symbol} data={data}/>);
                })}
            </DropDown>
        );
    }
});

і ось код з setstate (який створює нескінченний цикл)

let SearchDropdown = React.createClass({
    getInitialState() {
        return {
            top: false
        };
    },
    componentDidUpdate(params) {
        let el = this.getDOMNode();
        if (this.state.top) {
           this.setState({top: false});
        }
        if(needToMoveOnTop(el)) {
            el.top = newTopValue;
            el.right = newRightValue;
            if (!this.state.top) {
              this.setState({top: true});
           }
        }
    },
    render() {
        let dataFeed = this.props.dataFeed;
        let class = cx({'dropDown-top' : this.state.top});
        return (
            <DropDown className={class} >
                {dataFeed.map((data, i) => {
                    return (<DropDownRow key={response.symbol} data={data}/>);
                })}
            </DropDown>
        );
    }
});

9
Я думаю, що фокус у тому, що завждиsetState буде викликати повторну візуалізацію. Замість того, щоб перевіряти та дзвонити кілька разів, просто відслідковуйте те, що ви хочете бути в локальній змінній, а потім один раз в кінці виклику, лише якщо ваша локальна змінна не відповідає . Як зараз, ви відразу скидаєтесь після першого повторного відображення, яке ставить вас у нескінченний цикл. state.topsetStatestate.topcomponentDidUpdatesetStatestate.topstate.top
Ренді Морріс

2
Ознайомтесь з двома різними реалізаціями componentDidUpdateу цій загадці .
Ренді Морріс

блін! локальна змінна вирішує всю проблему, як я цього не зрозумів mysef! Дякую!
Катерина Павленко

1
Я думаю, ви повинні прийняти відповідь нижче. Якщо ви прочитаєте його ще раз, я думаю, що ви знайдете, що він достатньо відповідає на початкове запитання.
Ренді Морріс

Чому ніхто не запропонував перенести стан componentShouldUpdate?
Патрік Робертс

Відповіді:


116

Можна використовувати setStateвсередину componentDidUpdate. Проблема полягає в тому, що якимось чином ви створюєте нескінченний цикл, оскільки немає умови перерви.

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


4
що ви маєте на увазі під «умовою перерви»? перевірити, чи стан уже встановлено та не скидає його?
Катерина Павленко

Я погоджуюся з цим, моїм єдиним додатковим коментарем було б те, що додавання / видалення класів, ймовірно, непотрібне componentDidUpdateі може просто додаватися за потребою renderзамість цього.
Ренді Морріс

але додавання / видалення класу залежить від позиції випадаючого списку, яка зареєстрована в компонентDidUpdate, ви пропонуєте перевірити це двічі? І як я розумію, компонентDidUpdate називається ПІСЛЯ render (), тому додавати / видаляти клас у візуалізації ()
Катерина Павленко,

я додав свій код із setstate, чи можете ви перевірити його та вказати на мою помилку? або покажіть мені якийсь приклад, який би не викликав циклу
Катерина Павленко

2
компонентDidUpdate (prevProps, prevState) {якщо (prevState.x! == this.state.x) {// Зроби щось}}
Ashok R

68

componentDidUpdateПідпис void::componentDidUpdate(previousProps, previousState). За допомогою цього ви зможете перевірити, який реквізит / стан брудний, і зателефонувати setStateвідповідно.

Приклад:

componentDidUpdate(previousProps, previousState) {
    if (previousProps.data !== this.props.data) {
        this.setState({/*....*/})
    }
}

componentDidMountне має жодних аргументів і викликається лише тоді, коли створений компонент, тому його не можна використовувати для описаної мети.
Жуль

@Jules Дякую! Я писав componentDidMount, тож коли я написав відповідь, відоме ім’я каскадувало 😮 Знову дякую та чудово!
Abdennour TOUMI

componentDidUpdate(prevProps, prevState) { if ( prevState.x!== this.state.x) { //Do Something } }
Ашок Р

Я знаю вашу стурбованість @AshokR. Ви зменшуєте ім'я аргументу. але "prev" може означати запобігання не попереднього .. hhh. . жартую :)
Abdennour TOUMI

58

Якщо ви використовуєте setStateвсередині componentDidUpdateнього, він оновлює компонент, що призводить до виклику, componentDidUpdateякий згодом setStateзнову викликає, що призводить до нескінченного циклу. Вам слід умовно зателефонувати setStateта переконатися, що з часом виникає умова, що порушує виклик, наприклад:

componentDidUpdate: function() {
    if (condition) {
        this.setState({..})
    } else {
        //do something else
    }
}

Якщо ви оновлюєте компонент лише, надсилаючи до нього реквізити (він не оновлюється setState, за винятком випадку всередині компонентаDidUpdate), ви можете зателефонувати setStateвсередину componentWillReceivePropsзамість componentDidUpdate.


2
старе питання, але компонентWillReceiveProps застарілий і слід використовувати компонентWillRecieveProps. Ви не можете встановитиState всередині цього методу.
Брукс Дюбуа

Ви маєте на увазі getDerivedStateFromProps.
adi518

5

Цей приклад допоможе вам зрозуміти гачки життєвого циклу React .

Ви можете setStateв getDerivedStateFromPropsметоді, тобто, staticі запустити метод після зміни реквізиту componentDidUpdate.

У componentDidUpdateвас вийде 3-й парам, який повернеться з getSnapshotBeforeUpdate.

Ви можете перевірити це посилання codeandbox

// Child component
class Child extends React.Component {
  // First thing called when component loaded
  constructor(props) {
    console.log("constructor");
    super(props);
    this.state = {
      value: this.props.value,
      color: "green"
    };
  }

  // static method
  // dont have access of 'this'
  // return object will update the state
  static getDerivedStateFromProps(props, state) {
    console.log("getDerivedStateFromProps");
    return {
      value: props.value,
      color: props.value % 2 === 0 ? "green" : "red"
    };
  }

  // skip render if return false
  shouldComponentUpdate(nextProps, nextState) {
    console.log("shouldComponentUpdate");
    // return nextState.color !== this.state.color;
    return true;
  }

  // In between before real DOM updates (pre-commit)
  // has access of 'this'
  // return object will be captured in componentDidUpdate
  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log("getSnapshotBeforeUpdate");
    return { oldValue: prevState.value };
  }

  // Calls after component updated
  // has access of previous state and props with snapshot
  // Can call methods here
  // setState inside this will cause infinite loop
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log("componentDidUpdate: ", prevProps, prevState, snapshot);
  }

  static getDerivedStateFromError(error) {
    console.log("getDerivedStateFromError");
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    console.log("componentDidCatch: ", error, info);
  }

  // After component mount
  // Good place to start AJAX call and initial state
  componentDidMount() {
    console.log("componentDidMount");
    this.makeAjaxCall();
  }

  makeAjaxCall() {
    console.log("makeAjaxCall");
  }

  onClick() {
    console.log("state: ", this.state);
  }

  render() {
    return (
      <div style={{ border: "1px solid red", padding: "0px 10px 10px 10px" }}>
        <p style={{ color: this.state.color }}>Color: {this.state.color}</p>
        <button onClick={() => this.onClick()}>{this.props.value}</button>
      </div>
    );
  }
}

// Parent component
class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: 1 };

    this.tick = () => {
      this.setState({
        date: new Date(),
        value: this.state.value + 1
      });
    };
  }

  componentDidMount() {
    setTimeout(this.tick, 2000);
  }

  render() {
    return (
      <div style={{ border: "1px solid blue", padding: "0px 10px 10px 10px" }}>
        <p>Parent</p>
        <Child value={this.state.value} />
      </div>
    );
  }
}

function App() {
  return (
    <React.Fragment>
      <Parent />
    </React.Fragment>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>


2

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

Обов’язково встановіть такий стан:

let top = newValue /*true or false*/
if(top !== this.state.top){
    this.setState({top});
}

-1

У мене була подібна проблема, коли мені потрібно зосередити toolTip. Реагувати setState в компонентDidUpdate поклав мене у нескінченний цикл, і я спробував умову, що це спрацювало. Але я виявив, що використання зворотного виклику ref дало мені простіше і чистіше рішення, якщо ви використовуєте вбудовану функцію для зворотного зворотного виклику, ви зіткнетеся з нульовою проблемою для кожного оновлення компонентів. Тому використовуйте посилання функції у зворотному зворотному виклику та встановіть стан, який ініціює повторне відображення

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.