Actualización del estado en el cambio de accesorios en el Formulario de reacción

184

Tengo problemas con un formulario React y con la administración adecuada del estado. Tengo un campo de entrada de tiempo en un formulario (en un modal). El valor inicial se establece como una variable de estado getInitialStatey se pasa desde un componente primario. Esto en sí mismo funciona bien.

El problema surge cuando quiero actualizar el valor predeterminado de start_time a través del componente principal. La actualización en sí ocurre en el componente principal a través de setState start_time: new_time. Sin embargo, en mi forma, el valor predeterminado de start_time nunca cambia, ya que solo se define una vez getInitialState.

He tratado de usar componentWillUpdatepara forzar un cambio de estado setState start_time: next_props.start_time, lo que realmente funcionó, pero me dio Uncaught RangeError: Maximum call stack size exceedederrores.

Entonces mi pregunta es, ¿cuál es la forma correcta de actualizar el estado en este caso? ¿Estoy pensando en esto mal de alguna manera?

Código actual:

@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
David Basalla
fuente

Respuestas:

287

componentWillReceiveProps está depcricado ya que reacciona 16: use getDerivedStateFromProps en su lugar

Si entiendo correctamente, ¿tiene un componente principal que se está transmitiendo start_timeal ModalBodycomponente que lo asigna a su propio estado? Y desea actualizar ese tiempo desde el componente primario, no desde un componente secundario.

React tiene algunos consejos para lidiar con este escenario. (Tenga en cuenta que este es un artículo antiguo que desde entonces se ha eliminado de la web. Aquí hay un enlace al documento actual sobre accesorios de componentes ).

El uso de accesorios para generar estado a getInitialStatemenudo conduce a la duplicación de la "fuente de verdad", es decir, dónde están los datos reales. Esto se debe a getInitialStateque solo se invoca cuando se crea el componente por primera vez.

Siempre que sea posible, calcule los valores sobre la marcha para asegurarse de que no se desincronicen más adelante y causen problemas de mantenimiento.

Básicamente, cada vez que asigna padres propsa un niño, stateel método de representación no siempre se llama en la actualización de accesorios. Debe invocarlo manualmente, utilizando el componentWillReceivePropsmétodo

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 });
  }
}
Brad B
fuente
84
Obsoleta, ya que de Reaccionar 16
tio
77
@dude Todavía no está en desuso, a lo que te refieres es solo un aviso para futuras referencias. Cito[..]going to be deprecated in the future
paddotk
77
@poepje Puede que todavía no esté en desuso, pero el estándar actual lo considera inseguro y probablemente debería evitarse
despliega el
12
Entonces, ¿cuál debería ser la nueva forma de hacer esto después de que componentWillReceiveProps fuera obsoleto?
Boris D. Teoharov
55
@Boris Ahora, el equipo de reacción básicamente te dice que te llenes. Le dan un nuevo método, llamado getDerivedStateFromProps. El problema es que este es un método estático. Lo que significa que no puede hacer nada asíncrono para actualizar el estado (porque tiene que devolver el nuevo estado inmediatamente), tampoco puede acceder a los métodos de clase o campos. También puede usar la memorización, pero eso no se ajusta a todos los casos de uso. Una vez más, el equipo de reacción quiere forzar su forma de hacer las cosas. Es una decisión de diseño extremadamente estúpida e incapacitante.
ig-dev
76

Aparentemente las cosas están cambiando ... getDerivedStateFromProps () es ahora la función preferida.

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

(código anterior por danburzo @ github)

ErichBSchulz
fuente
77
Para su información, debe regresar también nullsi nada debería cambiar, así que justo después de su if, debe ir conreturn null
Ilgıt Yıldırım
@ IlgıtYıldırım - he editado el código desde que 4 personas han votado tu comentario - ¿realmente hace una diferencia?
ErichBSchulz
Hay un recurso bastante bueno que profundiza en las diferentes opciones y por qué usaría cualquiera de ellos getDerivedStateFromPropso la memorización reactjs.org/blog/2018/06/07/…
despliega el
2
getDerivedStateFromProps se ve obligado a ser estático. Lo que significa que no puede hacer nada asíncrono para actualizar el estado, tampoco puede acceder a métodos de clase o campos. Una vez más, el equipo de reacción quiere forzar su forma de hacer las cosas. Es una decisión de diseño extremadamente estúpida e incapacitante.
ig-dev
39

componentWillReceiveProps está en desuso porque su uso "a menudo genera errores e inconsistencias".

Si algo cambia desde el exterior, considere restablecer el componente hijo por completo conkey .

Proporcionar un keyaccesorio al componente secundario asegura que siempre que el valor de los keycambios desde el exterior, este componente se vuelva a representar. P.ej,

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

Sobre su rendimiento:

Si bien esto puede sonar lento, la diferencia de rendimiento suele ser insignificante. El uso de una clave puede incluso ser más rápido si los componentes tienen una lógica pesada que se ejecuta en las actualizaciones, ya que la diferenciación se omite para ese subárbol.

Lucia
fuente
1
¡La llave, el secreto! Funciona perfectamente en React 16 como se mencionó anteriormente
Darren Sweeney
la clave no funcionará, si es un objeto y no tiene una cadena única
user3468806
Key funciona para objetos, lo hice. Por supuesto, tenía una cadena única para la clave.
tsujp
@ user3468806 Si no es un objeto complejo con referencias externas, puede utilizar JSON.stringify(myObject)para derivar una clave única de su objeto.
Roy Prins
24

También hay componentDidUpdate disponible.

Función firmante:

componentDidUpdate(prevProps, prevState, snapshot)

Use esto como una oportunidad para operar en el DOM cuando el componente se haya actualizado. No se llama en la inicial render.

Vea que probablemente no necesite el artículo de estado derivado , que describe Anti-Pattern para ambos componentDidUpdatey getDerivedStateFromProps. Lo encontré muy útil.

arminfro
fuente
Termino usando componentDidUpdateporque es simple y es más adecuado para la mayoría de los casos.
KeitelDOG
14

La nueva forma de hacer esto es usar useEffect en lugar de componentWillReceiveProps de la manera anterior:

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

se convierte en lo siguiente en un componente impulsado por ganchos funcionales:

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

configuramos el estado usando setState, usando useEffect, verificamos los cambios en el accesorio especificado y tomamos la acción para actualizar el estado al cambiar el accesorio.

MMO
fuente
5

Probablemente no necesite estado derivado

1. Establecer una clave del padre

Cuando una clave cambia, React creará una nueva instancia de componente en lugar de actualizar la actual. Las claves se usan generalmente para listas dinámicas, pero también son útiles aquí.

2. Use getDerivedStateFromProps/componentWillReceiveProps

Si la clave no funciona por alguna razón (quizás el componente es muy costoso de inicializar)

Al usarlo getDerivedStateFromProps, puede restablecer cualquier parte del estado, ¡pero parece un poco defectuoso en este momento (v16.7) !, vea el enlace de arriba para el uso

Ghominejad
fuente
2

De la documentación de reacción: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

El estado de borrado cuando los accesorios cambian es un Anti Patrón

Desde React 16, componentWillReceiveProps está en desuso. De la documentación de reacción, el enfoque recomendado en este caso es el uso

  1. Componente totalmente controlado: el ParentComponentde la ModalBodyvoluntad posee el start_timeestado. Este no es mi enfoque preferido en este caso, ya que creo que el modal debería ser el propietario de este estado.
  2. Componente totalmente incontrolado con una clave: este es mi enfoque preferido. Un ejemplo de la documentación de reacción: https://codesandbox.io/s/6v1znlxyxn . Sería el propietario del start_timeestado ModalBodyy lo usaría getInitialStatetal como lo hizo anteriormente. Para restablecer el start_timeestado, simplemente cambie la clave delParentComponent
Lu Tran
fuente
0

Use Memoize

La derivación de estado de la operación es una manipulación directa de accesorios, sin necesidad de derivación verdadera. En otras palabras, si tiene un accesorio que se puede utilizar o transformar directamente, no es necesario almacenar el accesorio en estado .

Dado que el valor de estado de start_timees simplemente el accesorio start_time.format("HH:mm"), la información contenida en el accesorio ya es suficiente en sí misma para actualizar el componente.

Sin embargo, si solo deseaba llamar al formato en un cambio de utilería, la forma correcta de hacerlo según la última documentación sería mediante Memoize: https://reactjs.org/blog/2018/06/07/you-probably-dont- need-derivado-estado.html # what-about-memoization

DannyMoshe
fuente
-1

Creo que usar ref es seguro para mí, no necesito preocuparme por algún método anterior.

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;
        }
    }
}
Mayo Weather VN
fuente
Creo que esta respuesta es críptica (el código es difícil de leer y sin ninguna explicación / enlace al problema de OP) y no aborda el problema de OP, que es cómo manejar el estado inicial.
netchkin