Come accedere allo stato di bambino in React?


215

Ho la seguente struttura:

FormEditor- contiene più FieldEditor FieldEditor- modifica un campo del modulo e salva vari valori al suo interno nel suo stato

Quando si fa clic su un pulsante in FormEditor, voglio essere in grado di raccogliere informazioni sui campi da tutti i FieldEditorcomponenti, informazioni nel loro stato e avere tutto in FormEditor.

Ho preso in considerazione la possibilità di memorizzare le informazioni sui campi al di fuori dello FieldEditorstato e di metterle invece nello FormEditorstato. Tuttavia, ciò richiederebbe FormEditorascoltare ciascuno dei suoi FieldEditorcomponenti mentre cambiano e archiviare le loro informazioni nel suo stato.

Non posso semplicemente accedere allo stato dei bambini invece? È l'ideale?


4
"Non posso semplicemente accedere allo stato dei bambini? È l'ideale?" No. Lo stato è qualcosa di interno e non dovrebbe fuoriuscire all'esterno. Potresti implementare metodi di accesso per il tuo componente, ma anche quello non è l'ideale.
Felix Kling,

@FelixKling Quindi stai suggerendo che il modo ideale per la comunicazione bambino-genitore è solo eventi?
Moshe Revah,

3
Sì, gli eventi sono a senso unico. O avere un flusso di dati unidirezionale come promuove Flux: facebook.github.io/flux
Felix Kling

1
Se non hai intenzione di usare FieldEditors separatamente, salvare il loro stato FormEditorsuona bene. In questo caso, le tue FieldEditoristanze verranno visualizzate in base al propspassato dal loro editor di moduli, non dal loro state. Un modo più complesso ma flessibile sarebbe quello di creare un serializzatore che attraversi qualsiasi contenitore figlio e trovi tutte le FormEditoristanze tra loro e le serializzi in un oggetto JSON. L'oggetto JSON può essere facoltativamente nidificato (più di un livello) in base ai livelli di annidamento delle istanze nell'editor del modulo.
Meglio,

4
Penso che il "Lifting State Up" di React Docs sia probabilmente il modo più "reattivo" per farlo
icc97,

Risposte:


136

Se hai già il gestore onChange per i singoli FieldEditor, non vedo perché non puoi semplicemente spostare lo stato sul componente FormEditor e passare un callback da lì ai FieldEditor che aggiorneranno lo stato principale. A me sembra un modo più reattivo di farlo.

Qualcosa del genere forse:

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

Originale - versione pre-ganci:


2
Questo è utile per onChange, ma non tanto in onSubmit.
190290000 Ruble Man,

1
@ 190290000RubleMan Non sono sicuro di cosa intendi, ma poiché i gestori onChange sui singoli campi si assicurano di avere sempre i dati del modulo completo disponibili nel tuo stato nel componente FormEditor, onSubmit includerebbe semplicemente qualcosa del genere myAPI.SaveStuff(this.state);. Se elabori un po 'di più, forse posso darti una risposta migliore :)
Markus-ipse,

Supponiamo che io abbia un modulo con 3 campi di tipo diverso, ognuno con la propria convalida. Vorrei convalidare ciascuno di essi prima di inviarlo. Se la convalida non riesce, ogni campo che presenta un errore deve eseguire nuovamente il rendering. Non ho capito come farlo con la soluzione proposta, ma forse c'è un modo!
190290000 Ruble Man,

1
@ 190290000RubleMan Sembra un caso diverso rispetto alla domanda originale. Apri una nuova domanda con maggiori dettagli e forse un po 'di codice di esempio, e posso dare un'occhiata più tardi :)
Markus-ipse,

Potresti trovare utile questa risposta : usa hook di stato, copre dove dovrebbe essere creato lo stato, come evitare il re-rendering dei moduli su ogni sequenza di tasti, come eseguire la convalida dei campi, ecc.
Andrew

189

Appena prima di entrare nei dettagli su come è possibile accedere allo stato di un componente figlio, assicurarsi di leggere la risposta di Markus-ipse in merito a una soluzione migliore per gestire questo particolare scenario.

Se desideri davvero accedere allo stato dei figli di un componente, puoi assegnare una proprietà chiamata refa ciascun figlio. Esistono ora due modi per implementare i riferimenti: usare React.createRef()e richiamare i riferimenti.

utilizzando React.createRef()

Questo è attualmente il modo raccomandato per usare i riferimenti a partire da React 16.3 (Vedi i documenti per maggiori informazioni). Se stai usando una versione precedente, vedi sotto per quanto riguarda i riferimenti di callback.

Dovrai creare un nuovo riferimento nel costruttore del componente principale e quindi assegnarlo a un figlio tramite l' refattributo.

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

Per accedere a questo tipo di ref, devi utilizzare:

const currentFieldEditor1 = this.FieldEditor1.current;

Ciò restituirà un'istanza del componente montato in modo da poter quindi utilizzare currentFieldEditor1.stateper accedere allo stato.

Solo una breve nota per dire che se si utilizzano questi riferimenti su un nodo DOM anziché su un componente (ad es. <div ref={this.divRef} />) this.divRef.currentSi restituirà l'elemento DOM sottostante anziché un'istanza del componente.

Rif. Richiamata

Questa proprietà accetta una funzione di callback a cui viene passato un riferimento al componente collegato. Questo callback viene eseguito immediatamente dopo il montaggio o lo smontaggio del componente.

Per esempio:

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

In questi esempi il riferimento è memorizzato sul componente padre. Per chiamare questo componente nel tuo codice, puoi usare:

this.fieldEditor1

e quindi usare this.fieldEditor1.stateper ottenere lo stato.

Una cosa da notare, assicurati che il componente figlio sia stato reso prima di provare ad accedervi ^ _ ^

Come sopra, se si utilizzano questi riferimenti su un nodo DOM anziché su un componente (ad esempio <div ref={(divRef) => {this.myDiv = divRef;}} />), this.divRefverrà restituito l'elemento DOM sottostante anziché un'istanza del componente.

Ulteriori informazioni

Se vuoi saperne di più sulla proprietà ref di React, dai un'occhiata a questa pagina da Facebook.

Assicurati di leggere la sezione " Non abusare dei riferimenti " che dice che non dovresti usare quelli del bambino stateper "far accadere le cose".

Spero che questo aiuti ^ _ ^

Modifica: aggiunto React.createRef()metodo per la creazione di riferimenti. Codice ES5 rimosso.


5
Anche pulito piccolo bocconcino, è possibile chiamare funzioni da this.refs.yourComponentNameHere. L'ho trovato utile per cambiare stato tramite funzioni. Esempio: this.refs.textInputField.clearInput();
Dylan Pierce,

7
Attenzione (dai documenti): i riferimenti sono un ottimo modo per inviare un messaggio a una particolare istanza figlio in un modo che sarebbe scomodo da fare tramite lo streaming Reactive propse state. Tuttavia, non dovrebbero essere l'astrazione ideale per il flusso di dati attraverso l'applicazione. Per impostazione predefinita, utilizzare il flusso di dati Reattivo e salvare refs per i casi d'uso intrinsecamente non reattivi.
Lynn,

2
Ricevo solo lo stato definito all'interno del costruttore (stato non aggiornato)
Vlado Pandžić,

3
L'uso di refs ora viene classificato come " Componenti non controllati " con la raccomandazione di utilizzare "Componenti controllati"
icc97,

È possibile farlo se il componente figlio è un connectedcomponente Redux ?
jmancherje,

7

È il 2020 e molti di voi verranno qui alla ricerca di una soluzione simile ma con Hooks (sono fantastici!) E con gli ultimi approcci in termini di pulizia e sintassi del codice.

Quindi, come avevano affermato le risposte precedenti, l'approccio migliore a questo tipo di problema è mantenere lo stato al di fuori della componente figlio fieldEditor. Potresti farlo in diversi modi.

Il più "complesso" è con il contesto (stato) globale che sia i genitori che i figli possono accedere e modificare. È un'ottima soluzione quando i componenti sono molto profondi nella gerarchia dell'albero e quindi è costoso inviare oggetti di scena in ogni livello.

In questo caso penso che non ne valga la pena, e un approccio più semplice ci porterà i risultati che desideriamo, semplicemente usando il potente React.useState().

Approccio con il gancio React.useState (), molto più semplice rispetto ai componenti di classe

Come detto, affronteremo le modifiche e memorizzeremo i dati del nostro componente figlio fieldEditornel nostro genitore fieldForm. Per fare ciò invieremo un riferimento alla funzione che gestirà e applicherà le modifiche allo fieldFormstato, è possibile farlo con:

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

Si noti che React.useState({})restituirà un array con la posizione 0 come valore specificato in chiamata (oggetto vuoto in questo caso) e la posizione 1 come riferimento alla funzione che modifica il valore.

Ora con il componente figlio, FieldEditornon è nemmeno necessario creare una funzione con un'istruzione return, una costante snella con una funzione freccia lo farà!

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

Aaaa e abbiamo finito, niente di più, con solo questi due componenti funzionali di melma abbiamo il nostro obiettivo finale di "accedere" al nostro FieldEditorvalore figlio e mostrarlo nel nostro genitore.

Puoi controllare la risposta accettata da 5 anni fa e vedere come Hooks ha reso il codice React più snello (Di molto!).

Spero che la mia risposta ti aiuti a imparare e capire di più su Hooks, e se vuoi controllare un esempio funzionante eccolo qui .


4

Ora puoi accedere allo stato di InputField che è figlio di FormEditor.

Fondamentalmente ogni volta che si verifica una modifica dello stato del campo di input (figlio) otteniamo il valore dall'oggetto evento e quindi passiamo questo valore al genitore in cui è impostato lo stato nel genitore.

Al clic sul pulsante stiamo solo stampando lo stato dei campi di input.

Il punto chiave qui è che stiamo usando gli oggetti di scena per ottenere l'ID / valore del campo di input e anche per chiamare le funzioni che sono impostate come attributi nel campo di input mentre generiamo i campi di input figlio riutilizzabili.

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);

7
Non sono sicuro di poter votare questo. Cosa dici qui a cui non è già stata data risposta da @ Markus-ipse?
Alexander Bird,

4

Come hanno detto le risposte precedenti, prova a spostare lo stato su un componente principale e modificare lo stato tramite callback passati ai suoi figli.

Nel caso in cui si ha realmente bisogno di accedere ad uno stato infantile che viene dichiarato come un componente funzionale (ganci) è possibile dichiarare una ref nel componente genitore, poi passarla come ref attributo per il bambino, ma è necessario utilizzare React.forwardRef e quindi l'hook usaImperativeHandle per dichiarare una funzione che puoi chiamare nel componente genitore.

Dai un'occhiata al seguente esempio:

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]);
})

Quindi dovresti essere in grado di ottenere myState nel componente Parent chiamando: myRef.current.getMyState();

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.