Подтвердить что ты не робот

SetState() внутри компонентаDidUpdate()

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

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

Вот часть рабочего кода с getDOMNode (i немного забытая логика позиционирования для упрощения кода)

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>
        );
    }
});
4b9b3361

Ответ 1

Вы можете использовать setState внутри componentDidUpdate. Проблема в том, что каким-то образом вы создаете бесконечный цикл, потому что нет условия прерывания.

Исходя из того факта, что вам нужны значения, предоставляемые браузером после рендеринга компонента, я думаю, что ваш подход к использованию componentDidUpdate правильный, ему просто нужно лучше обработать условие, которое запускает setState.

Ответ 2

Если вы используете setState внутри componentDidUpdate, он обновляет компонент, в результате чего вызывается componentDidUpdate, который впоследствии снова вызывает setState, что приводит к бесконечному циклу. Вы должны условно позвонить setState и убедиться, что условие, нарушающее вызов, в конечном итоге возникает, например:

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

Если вы только обновляете компонент, отправляя ему реквизиты (он не обновляется setState, за исключением случая внутри componentDidUpdate), вы можете вызвать setState внутри componentWillReceiveProps вместо componentDidUpdate.

Ответ 3

Подпись componentDidUpdate является void::componentDidUpdate(previousProps, previousState). При этом вы сможете проверить, какие реквизиты/состояние грязные, и соответственно вызвать setState.

Пример:

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

Ответ 4

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

Обязательно установите свое состояние следующим образом:

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

Ответ 5

У меня была аналогичная проблема, когда я должен был центрировать toolTip. React setState в компонентеDidUpdate поместил меня в бесконечный цикл, я испробовал состояние, в котором он работал. Но я нашел, что использование в обратном вызове ref дало мне более простое и чистое решение, если вы используете встроенную функцию для обратного вызова ref, вы столкнетесь с нулевой проблемой для каждого обновления компонента. Поэтому используйте ссылку на функцию в обратном вызове ref и установите там состояние, которое будет инициировать повторную визуализацию

Ответ 6

В этом примере будут очищены зацепления жизненного цикла.

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

В componentDidUpdate вы получите третий параметр, который возвращается из getSnapshotBeforeUpdate.

// 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>