Как получить доступ к состоянию ребенка в React?

У меня есть следующая структура:

FormEditor - содержит несколько FieldEditor FieldEditor - редактирует поле формы и сохраняет в нем различные значения. Состояние

При нажатии кнопки в FormEditor я хочу иметь возможность собирать информацию о полях из всех компонентов FieldEditor, информацию, которая находится в их состоянии, и иметь все это в FormEditor.

Я рассмотрел сохранение информации о полях вне состояния FieldEditor и вместо этого поставил ее в состояние FormEditor. Однако для этого требуется FormEditor прослушать каждый из них FieldEditor, поскольку они меняются и сохраняют в нем свою информацию.

Не могу ли я просто получить доступ к состоянию детей? Идеально?

Ответ 1

Если у вас уже есть обработчик onChange для отдельных FieldEditors, я не понимаю, почему вы не могли просто переместить состояние в компонент FormEditor и просто передать обратный вызов оттуда в FieldEditors, который обновит родительское состояние. Мне кажется, это более эффективный способ сделать это.

Возможно, что-то в этом роде:

class FieldEditor extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    const text = event.target.value;
    this.props.onChange(this.props.id, text);
  }

  render() {
    return (
      <div className="field-editor">
        <input onChange={this.handleChange} value={this.props.value} />
      </div>
    );
  }
}

class FormEditor extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};

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

  handleFieldChange(fieldId, value) {
    this.setState({ [fieldId]: value });
  }

  render() {
    const fields = this.props.fields.map(field => (
      <FieldEditor
        key={field}
        id={field}
        onChange={this.handleFieldChange}
        value={this.state[field]}
      />
    ));

    return (
      <div>
        {fields}
        <div>{JSON.stringify(this.state)}</div>
      </div>
    );
  }
}

// Convert to class component and add ability to dynamically add/remove fields by having it in state
const App = () => {
  const fields = ["field1", "field2", "anotherField"];

  return <FormEditor fields={fields} />;
};

ReactDOM.render(<App />, document.body);

http://jsbin.com/qeyoxobixa/edit?js,output

Редактировать: просто ради этого, я переписал приведенный выше пример, используя хуки для всех, кто заинтересован:

const FieldEditor = ({ value, onChange, id }) => {
  const handleChange = event => {
    const text = event.target.value;
    onChange(id, text);
  };

  return (
    <div className="field-editor">
      <input onChange={handleChange} value={value} />
    </div>
  );
};

const FormEditor = props => {
  const [values, setValues] = useState({});
  const handleFieldChange = (fieldId, value) => {
    setValues({ ...values, [fieldId]: value });
  };
  const fields = props.fields.map(field => (
    <FieldEditor
      key={field}
      id={field}
      onChange={handleFieldChange}
      value={values[field]}
    />
  ));

  return (
    <div>
      {fields}
      <pre>{JSON.stringify(values, null, 2)}</pre>
    </div>
  );
};

// To add abillity to dynamically add/remove fields keep the list in state
const App = () => {
  const fields = ["field1", "field2", "anotherField"];

  return <FormEditor fields={fields} />;
};

Ответ 2

Непосредственно перед тем, как я подробно расскажу о том, как вы можете получить доступ к состоянию дочернего компонента, пожалуйста, прочитайте ответ Markus-ipse относительно лучшего решения для этого конкретного сценария.

Если вы действительно хотите получить доступ к состоянию дочерних компонентов, вы можете назначить свойство ref с именем каждого дочернего элемента. Теперь есть два способа реализации ссылок: использование React.createRef() и обратного вызова refs.

Использование React.createRef()

В настоящее время это рекомендуемый способ использования ссылок с React 16.3 (дополнительную информацию см. В документации). Если вы используете более раннюю версию, то смотрите ниже ссылки обратного вызова.

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

class FormEditor extends React.Component {
  constructor(props) {
    super(props);
    this.FieldEditor1 = React.createRef();
  }
  render() {
    return <FieldEditor ref={this.FieldEditor1} />;
  }
}

Чтобы получить доступ к этому виду ссылок, вам нужно использовать:

const currentFieldEditor1 = this.FieldEditor1.current;

Это вернет экземпляр смонтированного компонента, поэтому вы можете использовать currentFieldEditor1.state для доступа к состоянию.

Просто заметьте, что если вы используете эти ссылки на узле DOM вместо компонента (например, <div ref={this.divRef}/>), то this.divRef.current вернет базовый элемент DOM вместо компонента пример.

Обратный звонок

Это свойство принимает функцию обратного вызова, которой передается ссылка на присоединенный компонент. Этот обратный вызов выполняется сразу после монтирования или демонтажа компонента.

Например:

<FieldEditor
    ref={(fieldEditor1) => {this.fieldEditor1 = fieldEditor1;}
    {...props}
/>

В этих примерах ссылка хранится на родительском компоненте. Чтобы вызвать этот компонент в своем коде, вы можете использовать:

this.fieldEditor1

а затем используйте this.fieldEditor1.state чтобы получить состояние.

Прежде всего, убедитесь, что ваш дочерний компонент отрендерен, прежде чем пытаться получить к нему доступ ^ _ ^

Как и выше, если вы используете эти ссылки на узле DOM вместо компонента (например, <div ref={(divRef) => {this.myDiv = divRef;}}/>), то this.divRef вернет базовый элемент DOM вместо экземпляра компонента.

Дальнейшая информация

Если вы хотите узнать больше о React ref, проверьте эту страницу на Facebook.

Обязательно прочитайте раздел " Не злоупотребляйте ссылками ", в котором говорится, что вы не должны использовать дочернее state чтобы "заставить вещи происходить".

Надеюсь, это поможет ^ _ ^

Редактировать: Добавлен React.createRef() для создания ссылок. Удален код ES5.

Ответ 3

Теперь вы можете получить доступ к состоянию InputField, которое является дочерним по отношению к FormEditor.

По сути, всякий раз, когда происходит изменение состояния поля ввода (дочернего), мы получаем значение из объекта события и затем передаем это значение в Parent, где устанавливается состояние в Parent.

При нажатии кнопки мы просто печатаем состояние полей ввода.

Ключевым моментом здесь является то, что мы используем реквизиты для получения идентификатора/значения поля ввода, а также для вызова функций, которые задаются как атрибуты в поле ввода, в то время как мы генерируем многократно используемые дочерние поля ввода.

class InputField extends React.Component{
  handleChange = (event)=> {
    const val = event.target.value;
    this.props.onChange(this.props.id , val);
  }

  render() {
    return(
      <div>
        <input type="text" onChange={this.handleChange} value={this.props.value}/>
        <br/><br/>
      </div>
    );
  }
}       


class FormEditorParent extends React.Component {
  state = {};
  handleFieldChange = (inputFieldId , inputFieldValue) => {
    this.setState({[inputFieldId]:inputFieldValue});
  }
  //on Button click simply get the state of the input field
  handleClick = ()=>{
    console.log(JSON.stringify(this.state));
  }

  render() {
    const fields = this.props.fields.map(field => (
      <InputField
        key={field}
        id={field}
        onChange={this.handleFieldChange}
        value={this.state[field]}
      />
    ));

    return (
      <div>
        <div>
          <button onClick={this.handleClick}>Click Me</button>
        </div>
        <div>
          {fields}
        </div>
      </div>
    );
  }
}

const App = () => {
  const fields = ["field1", "field2", "anotherField"];
  return <FormEditorParent fields={fields} />;
};

ReactDOM.render(<App/>, mountNode);