Como acessar o estado da criança no React?

214

Eu tenho a seguinte estrutura:

FormEditor- contém vários FieldEditor FieldEditor- edita um campo do formulário e salva vários valores sobre ele no seu estado

Quando um botão é clicado no FormEditor, desejo poder coletar informações sobre os campos de todos os FieldEditorcomponentes, informações que estão em seu estado e tê-las no FormEditor.

Eu considerei armazenar as informações sobre os campos fora do FieldEditorestado de e, em FormEditorvez disso, colocá-las no estado de estado. No entanto, isso exigiria FormEditorouvir cada um de seus FieldEditorcomponentes à medida que eles mudam e armazenam suas informações em seu estado.

Não posso simplesmente acessar o estado das crianças? É ideal?

Moshe Revah
fonte
4
"Não posso simplesmente acessar o estado das crianças? É ideal?" Não. O estado é algo interno e não deve vazar para o exterior. Você pode implementar métodos de acesso para seu componente, mas mesmo isso não é o ideal.
Felix Kling
@FelixKling Então você está sugerindo que a maneira ideal de comunicação entre pais e filhos é apenas eventos?
Moshe Revah
3
Sim, os eventos são de sentido único. Ou ter um um de dados direcionais fluir como Flux promove: facebook.github.io/flux
Felix Kling
1
Se você não usar FieldEditors separadamente, é bom salvar o estado deles FormEditor. Se for esse o caso, suas FieldEditorinstâncias serão renderizadas com base no propspassado pelo editor de formulários, e não pelo state. Uma maneira mais complexa, porém flexível, seria criar um serializador que atravesse qualquer filho do contêiner e encontre todas as FormEditorinstâncias entre eles e os serialize em um objeto JSON. O objeto JSON pode opcionalmente ser aninhado (mais de um nível) com base nos níveis de aninhamento das instâncias no editor de formulários.
Meglio 24/01
4
Eu acho que a Reagir Docs 'elevação Estado Up' é provavelmente a maneira mais 'Reacty' de fazer isso
icc97

Respostas:

135

Se você já possui o manipulador onChange para os FieldEditors individuais, não vejo por que você não pode simplesmente mover o estado para o componente FormEditor e apenas passar um retorno de chamada para os FieldEditors que atualizarão o estado pai. Isso me parece uma maneira mais reativa de fazer isso.

Talvez algo assim:

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

Original - versão pré-ganchos:

Markus-ipse
fonte
2
Isso é útil no onChange, mas não tanto no onSubmit.
190290000 Ruble Man
1
@ 190290000RubleMan Não sei ao certo o que você quer dizer, mas como os manipuladores onChange nos campos individuais garantem que você sempre tenha os dados completos do formulário disponíveis no seu estado no componente FormEditor, seu onSubmit incluiria apenas algo como myAPI.SaveStuff(this.state);. Se você elaborar um pouco mais, talvez eu possa lhe dar uma resposta melhor :)
Markus-ipse
Digamos que eu tenha um formulário com 3 campos de tipo diferente, cada um com sua própria validação. Gostaria de validar cada um deles antes de enviar. Se a validação falhar, cada campo com erro deve ser renderizado novamente. Ainda não descobri como fazer isso com a solução proposta, mas talvez exista uma maneira!
190290000 Ruble Man
1
@ 190290000RubleMan Parece um caso diferente da pergunta original. Abra uma nova pergunta com mais detalhes e talvez algum código de exemplo, e eu posso ter um olhar mais tarde :)
Markus-ipse
Você pode encontrar esta resposta útil : ele usa ganchos do Estado, que abrange onde o estado deve ser criado, como evitar a forma de re-render em cada tecla, como fazer a validação de campo, etc.
Andrew
189

Antes de entrar em detalhes sobre como você pode acessar o estado de um componente filho, leia a resposta de Markus-ipse sobre uma solução melhor para lidar com esse cenário específico.

Se você realmente deseja acessar o estado dos filhos de um componente, pode atribuir uma propriedade chamada refa cada filho. Agora, existem duas maneiras de implementar referências: Usando React.createRef()e referências de retorno de chamada.

Usando React.createRef()

Atualmente, é a maneira recomendada de usar referências a partir do React 16.3 (consulte a documentação para obter mais informações). Se você estiver usando uma versão anterior, veja abaixo as referências de retorno de chamada.

Você precisará criar uma nova referência no construtor do componente pai e depois atribuí-la a um filho por meio do refatributo

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

Para acessar esse tipo de referência, você precisará usar:

const currentFieldEditor1 = this.FieldEditor1.current;

Isso retornará uma instância do componente montado para que você possa usar currentFieldEditor1.statepara acessar o estado.

Apenas uma observação rápida para dizer que, se você usar essas referências em um nó DOM em vez de em um componente (por exemplo <div ref={this.divRef} />), this.divRef.currentretornará o elemento DOM subjacente em vez de uma instância de componente.

Referências de retorno de chamada

Essa propriedade utiliza uma função de retorno de chamada que recebe uma referência ao componente conectado. Esse retorno de chamada é executado imediatamente após o componente ser montado ou desmontado.

Por exemplo:

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

Nestes exemplos, a referência é armazenada no componente pai. Para chamar esse componente no seu código, você pode usar:

this.fieldEditor1

e depois use this.fieldEditor1.statepara obter o estado.

Uma coisa a observar, verifique se o componente filho foi renderizado antes de tentar acessá-lo ^ _ ^

Como acima, se você usar essas referências em um nó DOM em vez de em um componente (por exemplo <div ref={(divRef) => {this.myDiv = divRef;}} />), this.divRefretornará o elemento DOM subjacente em vez de uma instância do componente.

Outras informações

Se você quiser ler mais sobre a propriedade ref do React, confira esta página no Facebook.

Leia a seção "Não use demais referências ", que diz que você não deve usar a criança statepara "fazer as coisas acontecerem".

Espero que isso ajude ^ _ ^

Edit: Adicionado React.createRef()método para criar refs. Código ES5 removido.

Martoid Prime
fonte
5
Também pequeno detalhe, você pode chamar funções de this.refs.yourComponentNameHere. Achei útil mudar o estado por meio de funções. Exemplo: this.refs.textInputField.clearInput();
Dylan Pierce
7
Cuidado (dos documentos): as referências são uma ótima maneira de enviar uma mensagem para uma instância filho específica de uma maneira que seria inconveniente através do streaming de Reativo propse state. No entanto, eles não devem ser sua abstração essencial para o fluxo de dados através de seu aplicativo. Por padrão, use o fluxo de dados Reativo e salve refs para casos de uso inerentemente não reativos.
21416 Lynn
2
Eu só fico estado que foi definido dentro do construtor (não até estado data)
Vlado Pandžić
3
O uso de refs agora é classificado como ' Componentes Não Controlados' com a recomendação de usar 'Componentes Controlados'
icc97
É possível fazer isso se o componente filho for um connectedcomponente Redux ?
jmancherje
6

Em 2020, muitos de vocês virão aqui procurando uma solução semelhante, mas com Hooks (Eles são ótimos!) E com as abordagens mais recentes em termos de limpeza e sintaxe do código.

Portanto, como as respostas anteriores declararam, a melhor abordagem para esse tipo de problema é manter o estado fora do componente filho fieldEditor. Você poderia fazer isso de várias maneiras.

O mais "complexo" é o contexto (estado) global que pai e filho podem acessar e modificar. É uma ótima solução quando os componentes são muito profundos na hierarquia da árvore e, portanto, é caro enviar adereços em cada nível.

Nesse caso, acho que não vale a pena, e uma abordagem mais simples nos trará os resultados que queremos, apenas usando os poderosos React.useState().

Abordagem com o gancho React.useState (), muito mais simples do que com os componentes da classe

Como dito, lidaremos com as alterações e armazenaremos os dados de nosso componente filho fieldEditorem nossos pais fieldForm. Para fazer isso, enviaremos uma referência à função que tratará e aplicará as alterações no fieldFormestado, você pode fazer isso com:

function FieldForm({ fields }) {
  const [fieldsValues, setFieldsValues] = React.useState({});
  const handleChange = (event, fieldId) => {
    let newFields = { ...fieldsValues };
    newFields[fieldId] = event.target.value;

    setFieldsValues(newFields);
  };

  return (
    <div>
      {fields.map(field => (
        <FieldEditor
          key={field}
          id={field}
          handleChange={handleChange}
          value={fieldsValues[field]}
        />
      ))}
      <div>{JSON.stringify(fieldsValues)}</div>
    </div>
  );
}

Observe que React.useState({})retornará uma matriz com a posição 0 sendo o valor especificado na chamada (neste caso, objeto vazio) e a posição 1 sendo a referência à função que modifica o valor.

Agora, com o componente filho FieldEditor, você nem precisa criar uma função com uma instrução de retorno, uma constante enxuta com uma função de seta serve!

const FieldEditor = ({ id, value, handleChange }) => (
  <div className="field-editor">
    <input onChange={event => handleChange(event, id)} value={value} />
  </div>
);

E pronto, nada mais, com apenas esses dois componentes funcionais do lodo, temos nosso objetivo final de "acessar" o FieldEditorvalor filho e exibi-lo em nossos pais.

Você pode verificar a resposta aceita de 5 anos atrás e ver como Hooks tornou o código do React mais enxuto (Por muito!).

Espero que minha resposta ajude você a aprender e entender mais sobre Hooks, e se você quiser verificar um exemplo de trabalho, aqui está .

Josep Vidal
fonte
4

Agora você pode acessar o estado do InputField, filho do FormEditor.

Basicamente, sempre que há uma alteração no estado do campo de entrada (filho), estamos obtendo o valor do objeto de evento e passando esse valor para o Pai onde o estado no Pai está definido.

Ao clicar no botão, estamos apenas imprimindo o estado dos campos de Entrada.

O ponto principal aqui é que estamos usando os adereços para obter o ID / valor do campo de entrada e também para chamar as funções definidas como atributos no campo de entrada enquanto geramos os campos filho reutilizáveis ​​de entrada.

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);
Dhana
fonte
7
Não tenho certeza se posso aprovar este. O que você menciona aqui que ainda não foi respondido por @ Markus-ipse?
Alexander Ave
3

Como as respostas anteriores disseram, tente mover o estado para um componente superior e modifique o estado através de retornos de chamada passados ​​para seus filhos.

Caso você realmente precise acessar um estado filho declarado como um componente funcional (ganchos), pode declarar uma referência no componente pai e passá-la como um atributo ref à criança, mas precisa usar React.forwardRef e, em seguida, o gancho useImperativeHandle para declarar uma função que você pode chamar no componente pai.

Veja o seguinte exemplo:

const Parent = () => {
    const myRef = useRef();
    return <Child ref={myRef} />;
}

const Child = React.forwardRef((props, ref) => {
    const [myState, setMyState] = useState('This is my state!');
    useImperativeHandle(ref, () => ({getMyState: () => {return myState}}), [myState]);
})

Então, você poderá obter myState no componente Pai chamando: myRef.current.getMyState();

Mauricio Avendaño
fonte