Volver a renderizar el componente React cuando cambie la propiedad

90

Estoy tratando de separar un componente de presentación de un componente de contenedor. Tengo un SitesTabley un SitesTableContainer. El contenedor es responsable de desencadenar acciones redux para buscar los sitios apropiados según el usuario actual.

El problema es que el usuario actual se recupera de forma asincrónica, después de que el componente contenedor se procesa inicialmente. Esto significa que el componente contenedor no sabe que necesita volver a ejecutar el código en su componentDidMountfunción, lo que actualizaría los datos para enviarlos a SitesTable. Creo que necesito volver a renderizar el componente contenedor cuando cambia uno de sus accesorios (usuario). ¿Cómo hago esto correctamente?

class SitesTableContainer extends React.Component {
    static get propTypes() {
      return {
        sites: React.PropTypes.object,
        user: React.PropTypes.object,
        isManager: React.PropTypes.boolean
      }
     }

    componentDidMount() {
      if (this.props.isManager) {
        this.props.dispatch(actions.fetchAllSites())
      } else {
        const currentUserId = this.props.user.get('id')
        this.props.dispatch(actions.fetchUsersSites(currentUserId))
      }  
    }

    render() {
      return <SitesTable sites={this.props.sites}/>
    }
}

function mapStateToProps(state) {
  const user = userUtils.getCurrentUser(state)

  return {
    sites: state.get('sites'),
    user,
    isManager: userUtils.isManager(user)
  }
}

export default connect(mapStateToProps)(SitesTableContainer);
David
fuente
tienes algunas otras funciones disponibles, como componentDidUpdate, o probablemente la que estás buscando, componentWillReceiveProps (nextProps) si quieres disparar algo cuando cambien los accesorios
thsorens
¿Por qué necesita volver a renderizar SitesTable si no cambia sus accesorios?
QoP
@QoP las acciones que se envían componentDidMountcambiarán el sitesnodo en el estado de la aplicación, que se pasa al SitesTable. El sitesnodo de SitesStable cambiará.
David
Oh, lo entiendo, voy a escribir la respuesta.
QoP
1
Cómo lograr esto en un componente funcional
yaswanthkoneri

Respuestas:

115

Tienes que agregar una condición en tu componentDidUpdatemétodo.

El ejemplo está usando fast-deep-equalpara comparar los objetos.

import equal from 'fast-deep-equal'

...

constructor(){
  this.updateUser = this.updateUser.bind(this);
}  

componentDidMount() {
  this.updateUser();
}

componentDidUpdate(prevProps) {
  if(!equal(this.props.user, prevProps.user)) // Check if it's a new user, you can also use some unique property, like the ID  (this.props.user.id !== prevProps.user.id)
  {
    this.updateUser();
  }
} 

updateUser() {
  if (this.props.isManager) {
    this.props.dispatch(actions.fetchAllSites())
  } else {
    const currentUserId = this.props.user.get('id')
    this.props.dispatch(actions.fetchUsersSites(currentUserId))
  }  
}

Usando Hooks (React 16.8.0+)

import React, { useEffect } from 'react';

const SitesTableContainer = ({
  user,
  isManager,
  dispatch,
  sites,
}) => {
  useEffect(() => {
    if(isManager) {
      dispatch(actions.fetchAllSites())
    } else {
      const currentUserId = user.get('id')
      dispatch(actions.fetchUsersSites(currentUserId))
    }
  }, [user]); 

  return (
    return <SitesTable sites={sites}/>
  )

}

Si el accesorio que está comparando es un objeto o una matriz, debe usar en useDeepCompareEffectlugar de useEffect.

QoP
fuente
Tenga en cuenta que JSON.stringify solo se puede usar para este tipo de comparación, si es estable (por especificación no lo es), por lo que produce la misma salida para las mismas entradas. Recomiendo comparar las propiedades de identificación de los objetos de usuario, o pasar userId-s en los accesorios y compararlos, para evitar recargas innecesarias.
László Kardinál
4
Tenga en cuenta que el método del ciclo de vida componentWillReceiveProps está obsoleto y probablemente se eliminará en React 17. El uso de una combinación de componentDidUpdate y el nuevo método getDerivedStateFromProps es la estrategia sugerida por el equipo de desarrollo de React. Más información en la publicación de su blog: reactjs.org/blog/2018/03/27/update-on-async-rendering.html
michaelpoltorak
@QoP El segundo ejemplo, con React Hooks, ¿se desmontará y volverá a montar cada vez que usercambie? ¿Qué tan caro es esto?
Robotron
30

ComponentWillReceiveProps()va a quedar obsoleto en el futuro debido a errores e inconsistencias. Una solución alternativa para volver a renderizar un componente en el cambio de accesorios es usar ComponentDidUpdate()y ShouldComponentUpdate().

ComponentDidUpdate()se llama siempre que el componente se actualiza Y si ShouldComponentUpdate()devuelve verdadero (si ShouldComponentUpdate()no está definido, vuelve truede forma predeterminada).

shouldComponentUpdate(nextProps){
    return nextProps.changedProp !== this.state.changedProp;
}

componentDidUpdate(props){
    // Desired operations: ex setting state
}

Este mismo comportamiento se puede lograr usando solo el ComponentDidUpdate()método incluyendo la declaración condicional dentro de él.

componentDidUpdate(prevProps){
    if(prevProps.changedProp !== this.props.changedProp){
        this.setState({          
            changedProp: this.props.changedProp
        });
    }
}

Si uno intenta establecer el estado sin un condicional o sin definir, ShouldComponentUpdate()el componente se volverá a renderizar infinitamente

ob1
fuente
2
Esta respuesta debe ser votada a favor (al menos por ahora) ya que componentWillReceivePropsestá a punto de quedar obsoleta y se sugiere que no se use.
AnBisw
La segunda forma (declaración condicional dentro de componentDidUpdate) funciona para mí porque quiero que se produzcan otros cambios de estado, por ejemplo, cerrar un mensaje flash.
Little Brain
11

Puede usar KEYuna clave única (combinación de los datos) que cambia con los accesorios, y ese componente se volverá a representar con los accesorios actualizados.

Rafi
fuente
4
componentWillReceiveProps(nextProps) { // your code here}

Creo que ese es el evento que necesitas. componentWillReceivePropsse activa cada vez que su componente recibe algo a través de accesorios. A partir de ahí, puede tener su control y luego hacer lo que quiera hacer.

mr.b
fuente
11
componentWillReceivePropsobsoleto *
Maihan Nijat
2

Recomendaría echar un vistazo a esta respuesta mía y ver si es relevante para lo que está haciendo. Si entiendo tu problema real, es que simplemente no estás usando tu acción asincrónica correctamente y actualizando la "tienda" de redux, que actualizará automáticamente tu componente con sus nuevos accesorios.

Esta sección de su código:

componentDidMount() {
      if (this.props.isManager) {
        this.props.dispatch(actions.fetchAllSites())
      } else {
        const currentUserId = this.props.user.get('id')
        this.props.dispatch(actions.fetchUsersSites(currentUserId))
      }  
    }

No debe activarse en un componente, debe manejarse después de ejecutar su primera solicitud.

Eche un vistazo a este ejemplo de redux-thunk :

function makeASandwichWithSecretSauce(forPerson) {

  // Invert control!
  // Return a function that accepts `dispatch` so we can dispatch later.
  // Thunk middleware knows how to turn thunk async actions into actions.

  return function (dispatch) {
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop', forPerson, error))
    );
  };
}

No necesariamente tiene que usar redux-thunk, pero lo ayudará a razonar sobre escenarios como este y escribir código para que coincida.

TameBadger
fuente
bien, lo entiendo. Pero, ¿a dónde envía exactamente el makeASandwichWithSecretSauce en su componente?
David
Lo vincularé a un repositorio con un ejemplo relevante, ¿usa react-router con su aplicación?
TameBadger
@David también agradecería el enlace a ese ejemplo, tengo básicamente el mismo problema.
SamYoungNY
0

Un método amigable para usar es el siguiente, una vez que se actualice la propiedad, automáticamente volverá a renderizar el componente:

render {

let textWhenComponentUpdate = this.props.text 

return (
<View>
  <Text>{textWhenComponentUpdate}</Text>
</View>
)

}
0x01 Cerebro
fuente