Aggiornamento dello stato sul cambio di oggetti di scena in React Form


184

Sto riscontrando problemi con un modulo React e gestendo correttamente lo stato. Ho un campo di immissione del tempo in una forma (in modale). Il valore iniziale viene impostato come variabile di stato in getInitialStatee viene passato da un componente padre. Questo di per sé funziona bene.

Il problema si presenta quando voglio aggiornare il valore start_time predefinito tramite il componente padre. L'aggiornamento stesso avviene nel componente padre tramite setState start_time: new_time. Tuttavia, nella mia forma, il valore start_time predefinito non cambia mai, poiché viene definito solo una volta getInitialState.

Ho provato a usare componentWillUpdateper forzare un cambiamento di stato setState start_time: next_props.start_time, che in realtà ha funzionato, ma mi ha dato Uncaught RangeError: Maximum call stack size exceedederrori.

Quindi la mia domanda è: qual è il modo corretto di aggiornare lo stato in questo caso? Sto pensando a questo torto in qualche modo?

Codice attuale:

@ModalBody = React.createClass
  getInitialState: ->
    start_time: @props.start_time.format("HH:mm")

  #works but takes long and causes:
  #"Uncaught RangeError: Maximum call stack size exceeded"
  componentWillUpdate: (next_props, next_state) ->
    @setState(start_time: next_props.start_time.format("HH:mm"))

  fieldChanged: (fieldName, event) ->
    stateUpdate = {}
    stateUpdate[fieldName] = event.target.value
    @setState(stateUpdate)

  render: ->
    React.DOM.div
      className: "modal-body"
      React.DOM.form null,
        React.createElement FormLabelInputField,
          type: "time"
          id: "start_time"
          label_name: "Start Time"
          value: @state.start_time
          onChange: @fieldChanged.bind(null, "start_time”)

@FormLabelInputField = React.createClass
  render: ->
    React.DOM.div
      className: "form-group"
      React.DOM.label
        htmlFor: @props.id
        @props.label_name + ": "
      React.DOM.input
        className: "form-control"
        type: @props.type
        id: @props.id
        value: @props.value
        onChange: @props.onChange

Risposte:


287

componentWillReceiveProps è depacrato dalla reazione 16: utilizzare invece getDerivedStateFromProps

Se ho capito bene, hai un componente genitore che passa start_timeal ModalBodycomponente che lo assegna al suo stato? E vuoi aggiornare quel tempo dal genitore, non da un componente figlio.

React ha alcuni suggerimenti su come affrontare questo scenario. (Nota, questo è un vecchio articolo che è stato rimosso dal Web. Ecco un link al documento attuale sui componenti di supporto ).

L'uso di oggetti di scena per generare stato getInitialStatespesso porta alla duplicazione della "fonte della verità", ovvero dove si trovano i dati reali. Questo perché getInitialStateviene invocato solo quando il componente viene creato per la prima volta.

Quando possibile, calcola i valori al volo per assicurarti che non si sincronizzino in seguito e causino problemi di manutenzione.

Fondamentalmente, ogni volta che si assegna un genitore propsa un figlio, stateil metodo di rendering non viene sempre chiamato su aggiornamento prop. Devi invocarlo manualmente, usando il componentWillReceivePropsmetodo

componentWillReceiveProps(nextProps) {
  // You don't have to do this check first, but it can help prevent an unneeded render
  if (nextProps.startTime !== this.state.startTime) {
    this.setState({ startTime: nextProps.startTime });
  }
}

84
Deprecato a partire da React 16
amico

7
@dude Non è ancora deprecato, quello a cui ti riferisci è solo un avvertimento per riferimento futuro. Cito[..]going to be deprecated in the future
Paddotk,

7
@poepje Potrebbe non essere ancora deprecato, ma è considerato non sicuro dallo standard attuale e probabilmente dovrebbe essere evitato
chiarisce il

12
Quindi, quale dovrebbe essere il nuovo modo di fare questo dopo che componentWillReceiveProps è stato deprecato?
Boris D. Teoharov,

5
@Boris Ora il team di reazione ti sta sostanzialmente dicendo di farcire. Ti danno un nuovo metodo, chiamato getDerivedStateFromProps. Il trucco è che questo è un metodo statico. Ciò significa che non è possibile eseguire alcuna operazione asincrona per aggiornare lo stato (poiché è necessario restituire immediatamente il nuovo stato), né è possibile accedere ai metodi o ai campi della classe. Puoi anche usare la memoizzazione, ma non si adatta a tutti i casi d'uso. Ancora una volta, il team di reazione vuole forzare il loro modo di fare le cose. È una decisione di progettazione estremamente stupida e invalidante.
ig-dev,

76

Apparentemente le cose stanno cambiando .... getDerivedStateFromProps () è ora la funzione preferita.

class Component extends React.Component {
  static getDerivedStateFromProps(props, current_state) {
    if (current_state.value !== props.value) {
      return {
        value: props.value,
        computed_prop: heavy_computation(props.value)
      }
    }
    return null
  }
}

(sopra il codice di danburzo @ github)


7
Cordiali saluti, è necessario tornare anche nullse nulla dovrebbe cambiare così subito dopo il tuo if, dovresti andare conreturn null
Ilgıt Yıldırım

@ IlgıtYıldırım - hai modificato il codice da quando 4 persone hanno votato il tuo commento - fa davvero la differenza?
ErichBSchulz,

Esiste una risorsa abbastanza buona che approfondisce le diverse opzioni e il motivo per cui dovresti usare getDerivedStateFromPropso la memoization reajs.org/blog/2018/06/07/…
unflores

2
getDerivedStateFromProps è costretto a essere statico. Significa che non puoi fare nulla di asincrono per aggiornare lo stato, né puoi accedere a metodi o campi di classe. Ancora una volta, il team di reazione vuole forzare il loro modo di fare le cose. È una decisione di progettazione estremamente stupida e invalidante.
ig-dev,

39

componentWillReceiveProps è deprecato perché usarlo "spesso porta a bug e incoerenze".

Se qualcosa cambia dall'esterno, prendere in considerazione la possibilità di ripristinare completamente il componente figliokey .

Fornire un keysupporto al componente figlio assicura che ogni volta che il valore delle keymodifiche dall'esterno, questo componente viene ridistribuito. Per esempio,

<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>

Sulla sua prestazione:

Sebbene ciò possa sembrare lento, la differenza di prestazioni è generalmente insignificante. L'uso di una chiave può anche essere più veloce se i componenti hanno una logica pesante che viene eseguita sugli aggiornamenti poiché la differenza viene ignorata per quella sottostruttura.


1
La chiave, il segreto! Funziona perfettamente in React 16 come menzionato sopra
Darren Sweeney,

La chiave non funzionerà, se è un oggetto e non hai una stringa univoca
user3468806,

Key funziona per gli oggetti, l'ho fatto. Ovviamente avevo una stringa unica per la chiave.
tsujp,

@ user3468806 Se non è un oggetto complesso con riferimenti esterni, è possibile utilizzare JSON.stringify(myObject)per derivare una chiave univoca dall'oggetto.
Roy Prins,

24

È disponibile anche componentDidUpdate .

Signatur funzione:

componentDidUpdate(prevProps, prevState, snapshot)

Utilizzalo come un'opportunità per operare sul DOM quando il componente è stato aggiornato. Non viene chiamato all'iniziale render.

Ci vediamo probabilmente non è necessario l' articolo sullo stato derivato , che descrive l'anti-pattern per entrambi componentDidUpdatee getDerivedStateFromProps. L'ho trovato molto utile.


Finisco per usare componentDidUpdateperché è semplice ed è più adatto per la maggior parte dei casi.
KeitelDOG

14

Il nuovo modo di fare hook è quello di utilizzare useEffect invece di componentWillReceiveProps alla vecchia maniera:

componentWillReceiveProps(nextProps) {
  // You don't have to do this check first, but it can help prevent an unneeded render
  if (nextProps.startTime !== this.state.startTime) {
    this.setState({ startTime: nextProps.startTime });
  }
}

diventa il seguente in un componente guidato da ganci funzionali:

// store the startTime prop in local state
const [startTime, setStartTime] = useState(props.startTime)
// 
useEffect(() => {
  if (props.startTime !== startTime) {
    setStartTime(props.startTime);
  }
}, [props.startTime]);

impostiamo lo stato usando setState, usando useEffect controlliamo le modifiche all'elica specificata e intraprendiamo l'azione per aggiornare lo stato al cambio dell'elica.


5

Probabilmente non hai bisogno dello stato derivato

1. Impostare una chiave dal genitore

Quando una chiave cambia, React creerà una nuova istanza del componente anziché aggiornare quella corrente. Le chiavi vengono generalmente utilizzate per gli elenchi dinamici, ma sono utili anche qui.

2. Usa getDerivedStateFromProps/componentWillReceiveProps

Se la chiave non funziona per qualche motivo (forse il componente è molto costoso da inizializzare)

Usando getDerivedStateFromPropspuoi resettare qualsiasi parte dello stato ma sembra un po 'difettoso in questo momento (v16.7) !, vedi il link sopra per l'uso


2

Dalla documentazione di reazione: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

Lo stato di cancellazione quando si cambia il puntello è un Anti Pattern

Da React 16, componentWillReceiveProps è obsoleto. Dalla documentazione di reazione, l'approccio raccomandato in questo caso è l'uso

  1. Componente completamente controllato: ParentComponentla ModalBodyvolontà possederà lo start_timestato. Questo non è il mio approccio preferito in questo caso poiché penso che il modale dovrebbe possedere questo stato.
  2. Componente completamente incontrollato con una chiave: questo è il mio approccio preferito. Un esempio dalla documentazione di reazione: https://codesandbox.io/s/6v1znlxyxn . Avresti il ​​pieno possesso dello start_timestato dal tuo ModalBodye utilizzalo getInitialStateproprio come hai già fatto. Per ripristinare lo start_timestato, è sufficiente modificare la chiave daParentComponent


0

Usa Memoize

La derivazione dello stato da parte dell'op è una manipolazione diretta degli oggetti di scena, senza necessità di una vera derivazione. In altre parole, se si dispone di un puntello che può essere utilizzato o trasformato direttamente, non è necessario memorizzarlo sullo stato .

Dato che il valore dello stato di start_timeè semplicemente il prop start_time.format("HH:mm"), le informazioni contenute nel prop sono già di per sé sufficienti per aggiornare il componente.

Tuttavia, se volessi chiamare il formato solo su un cambio di prop, il modo corretto per farlo secondo l'ultima documentazione sarebbe tramite Memoize: https://reactjs.org/blog/2018/06/07/you-probably-dont- necessità di derivazione-state.html # what-about-Memoizzazione


-1

Penso che usare ref sia sicuro per me, non ho bisogno di cure per qualche metodo sopra.

class Company extends XComponent {
    constructor(props) {
        super(props);
        this.data = {};
    }
    fetchData(data) {
        this.resetState(data);
    }
    render() {
        return (
            <Input ref={c => this.data['name'] = c} type="text" className="form-control" />
        );
    }
}
class XComponent extends Component {
    resetState(obj) {
        for (var property in obj) {
            if (obj.hasOwnProperty(property) && typeof this.data[property] !== 'undefined') {
                if ( obj[property] !== this.data[property].state.value )
                    this.data[property].setState({value: obj[property]});
                else continue;
            }
            continue;
        }
    }
}

Penso che questa risposta sia criptica (il codice è difficilmente leggibile e senza alcuna spiegazione / collegamento al problema di OP) e non affronta il problema di OP, che è come gestire lo stato iniziale.
netchkin,
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.