may-cat
@may-cat

Странное поведение React. Состояние дочерних элементов то само прокидывается, то нет. Почему?

Я начал разбираться с react.js и у меня очень странная проблема, которая противоречит документации.

Задача - сделать редаткор markdown файла, который позволит редактировать аттрибуты файла.

Я сделал App и разместил ам несколько компонентов например, IntegerField. Вот код:

https://github.com/may-cat/markdown-editor/blob/ma...

И вот данные, на основе которых реакт рендерит форму:

https://github.com/may-cat/markdown-editor/blob/ma...

Когда я меняю значение в IntegerField у него срабатывает вот это коллбэк:

https://github.com/may-cat/markdown-editor/blob/ma...

(ну, на самом деле не у IntegerField's, а у его родителя), метод onFieldChange() вот он:

https://github.com/may-cat/markdown-editor/blob/ma...

Метод onFieldChange() ТОЛЬКО делает setState для компонента IntegerField, т.е. для дочернего компонента. App ничего об этом изменении знать не должен (ну по крайней мере так говорит вся документация на реакт)

И вот я делаю сабмит формы

https://github.com/may-cat/markdown-editor/blob/ma...

Чтобы посмотреть свойства у родительского компонента, App. И... начинается мистика.

У IntegerField, у которого есть "multiple" свойство, значение прокидывается в state App-а. Например, поле "businessvalue".

А у IntegerField без свойства "multiple" значение НЕ прокидывается в state App-а! Например, для поля "numbers".

Почему так? Что я делаю неправильно? Как это отладить?
  • Вопрос задан
  • 133 просмотра
Решения вопроса 1
rockon404
@rockon404 Куратор тега React
Frontend Developer
Вам бы основы JavaScript подтянуть.
Во-первых вы биндите контекст хандлера на AppField в конструкторе:
constructor(props) {
  super(props);
  this.state = props.params;
  this.onFieldChange = this.onFieldChange.bind(this); // тут
}

Во-вторых, вы определяте переменную self, ссылающуюся на this и она ни делает ровным счетом ничего, так как вы передаете функцию в колбек обработчика события:
renderSingleValue(data = {}) {
  ...
  let self = this;  // бессмысленно

  return (
    <input type="text"
      ...
      onChange={self.onFieldChange} 
      ...
    />
  );
}

Даже если бы функция не была забинжена на AppField, у вас бы все-равно ничего не получилось.

В-третих, значение инпутов лучше хранить в родителе, а не в самих инпутах.

Трюк с self работает в отложенных вызовах:
constructor() {
  const self = this;

  node.addEventListener('click', function() {
    self.handleEvent();  // сработает, так как функция вызывается на self
  });
}

и не сработает при передаче функции:
constructor() {
  const self = this;

  node.addEventListener('click', self.handleEvent);  // не сработает, функция передается
                                                     // в колбек обработчика события
}                                                    // и не будет вызываться на self


Пример как можно сделать контролируемую форму с состоянием инпутов в родителе:
class Example extends Component {
  state = {
    inputValue: '',
  };

  handleChange = e => {  // arrow class field function биндится на контекст экземпляра
    const { name, value } = e.target;
    
    this.setState({
      [name]: value,
    });
  };

  render() {
    const { inputValue } = this.state;

    return (
      <Wrapper>
        <input
          name="inputValue"
          value={inputValue}
          onChange={this.handleChange}
        />
        ...
      </Wrapper>
    );
  }
}

Аналогичное решение без использования class field function:
class Example extends Component {
  constructor(props) {
    super(props);
    this.state = {
      inputValue: '',
    };
    this.handleChange = this.handleChange.bind(this);
  }
  

  handleChange(e) {
    const { name, value } = e.target;
    
    this.setState({
      [name]: value,
    });
  }

  render() {
    const { inputValue } = this.state;

    return (
      <Wrapper>
        <input
          name="inputValue"
          value={inputValue}
          onChange={this.handleChange}
        />
        ...
      </Wrapper>
    );
  }
}


Если делаете совой компонент вроде кастомного select, то вы можете в его реализации по изменению сами вызывать хандлер onChange, передавая туда фейковое событие с нужными вам ключами:
handleChange = value => {
  const { name, onChange } = this.props;
  const fakeEvent = { target: { name, value } };

  onChange(fakeEvent);
};
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы