Ponownie wyrenderuj komponent React po zmianie właściwości

90

Próbuję oddzielić składnik prezentacji od składnika kontenera. Mam SitesTablei SitesTableContainer. Kontener jest odpowiedzialny za wyzwalanie działań redukujących w celu pobrania odpowiednich witryn na podstawie bieżącego użytkownika.

Problem polega na tym, że bieżący użytkownik jest pobierany asynchronicznie, po początkowym renderowaniu komponentu kontenera. Oznacza to, że komponent kontenera nie wie, że musi ponownie wykonać kod w swojej componentDidMountfunkcji, który zaktualizowałby dane do wysłania do SitesTable. Myślę, że muszę ponownie wyrenderować komponent kontenera, gdy zmieni się jeden z jego właściwości (użytkownika). Jak mam to zrobić poprawnie?

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
źródło
masz kilka innych dostępnych funkcji, takich jak componentDidUpdate lub prawdopodobnie ta, której szukasz, componentWillReceiveProps (nextProps), jeśli chcesz odpalić coś, gdy zmieniają się rekwizyty
thsorens
Dlaczego musisz ponownie renderować SitesTable, jeśli nie zmienia on swoich właściwości?
QoP
@QoP wywoływane akcje componentDidMountzmienią siteswęzeł w stanie aplikacji, który jest przekazywany do SitesTable. Węzeł SitesStable sitesbędzie się zmieniał.
David
Och, rozumiem, napiszę odpowiedź.
QoP
1
Jak to osiągnąć w komponencie funkcjonalnym
yaswanthkoneri

Odpowiedzi:

115

Musisz dodać warunek w swojej componentDidUpdatemetodzie.

Przykład używa fast-deep-equaldo porównania obiektów.

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

Używanie hooków (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}/>
  )

}

Jeśli porównywany atrybut jest obiektem lub tablicą, powinieneś użyć useDeepCompareEffectzamiast useEffect.

QoP
źródło
Zauważ, że JSON.stringify może być używany tylko do tego rodzaju porównania, jeśli jest stabilny (zgodnie ze specyfikacją nie jest), więc generuje te same dane wyjściowe dla tych samych danych wejściowych. Zalecam porównywanie właściwości id obiektów użytkownika lub przekazywanie userId-s we właściwościach i porównywanie ich, aby uniknąć niepotrzebnych przeładowań.
László Kardinál
4
Zwróć uwagę, że metoda cyklu życia componentWillReceiveProps jest przestarzała i prawdopodobnie zostanie usunięta w React 17. Używanie kombinacji componentDidUpdate i nowej metody getDerivedStateFromProps jest sugerowaną strategią zespołu deweloperskiego React. Więcej w swoim blogu: reactjs.org/blog/2018/03/27/update-on-async-rendering.html
michaelpoltorak
@QoP Drugi przykład, z Hookami React, czy odmontuje i zamontuje ponownie po każdej userzmianie? Ile to kosztuje?
Robotron
30

ComponentWillReceiveProps()w przyszłości zostanie uznany za przestarzały z powodu błędów i niespójności. Alternatywnym rozwiązaniem ponownego renderowania komponentu przy zmianie rekwizytów jest użycie ComponentDidUpdate()i ShouldComponentUpdate().

ComponentDidUpdate()jest wywoływana za każdym razem, gdy składnik aktualizuje ORAZ jeśli ShouldComponentUpdate()zwraca wartość true (jeśli ShouldComponentUpdate()nie jest zdefiniowana, zwraca wartość truedomyślną).

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

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

To samo zachowanie można osiągnąć tylko przy użyciu ComponentDidUpdate()metody, umieszczając w niej instrukcję warunkową.

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

Jeśli ktoś spróbuje ustawić stan bez warunku lub bez definiowania ShouldComponentUpdate()komponentu, zostanie nieskończenie ponownie renderowany

ob1
źródło
2
Ta odpowiedź musi zostać zaaprobowana (przynajmniej na razie), ponieważ componentWillReceivePropswkrótce zostanie wycofana i jest sugerowana przeciwko użyciu.
AnBisw
Druga forma (instrukcja warunkowa wewnątrz componentDidUpdate) działa dla mnie, ponieważ chcę, aby inne zmiany stanu nadal występowały, np. Zamknięcie wiadomości flash.
Little Brain
11

Możesz użyć KEYunikalnego klucza (kombinacji danych), który zmienia się wraz z właściwościami, a ten komponent zostanie ponownie wyrenderowany ze zaktualizowanymi właściwościami.

Rafi
źródło
4
componentWillReceiveProps(nextProps) { // your code here}

Myślę, że to wydarzenie, którego potrzebujesz. componentWillReceivePropsuruchamia się, gdy komponent otrzyma coś przez rekwizyty. Stamtąd możesz sprawdzić, a następnie zrobić, co chcesz.

pan b
źródło
11
componentWillReceivePropsprzestarzałe *
Maihan Nijat
2

Poleciłbym przyjrzeć się mojej odpowiedzi i sprawdzić, czy ma ona znaczenie dla tego, co robisz. Jeśli rozumiem twój prawdziwy problem, jest to, że po prostu nie używasz poprawnie akcji asynchronicznej i nie aktualizujesz "sklepu" redux, który automatycznie zaktualizuje twój komponent o nowe rekwizyty.

Ta sekcja twojego kodu:

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

Nie powinno być wyzwalane w komponencie, powinno być obsługiwane po wykonaniu pierwszego żądania.

Spójrz na ten przykład z 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))
    );
  };
}

Nie musisz koniecznie używać redux-thunk, ale pomoże ci to wnioskować w takich scenariuszach i pisać dopasowany kod.

TameBadger
źródło
dobrze, rozumiem. Ale gdzie dokładnie wysyłasz makeASandwichWithSecretSauce swój komponent?
David
Podlinię Cię do repozytorium z odpowiednim przykładem, czy używasz routera reaktywnego w swojej aplikacji?
TameBadger
@David również byłby wdzięczny za link do tego przykładu, mam w zasadzie ten sam problem.
SamYoungNY
0

Przyjazna metoda jest następująca, ponieważ po zaktualizowaniu właściwości automatycznie wyrenderuje komponent:

render {

let textWhenComponentUpdate = this.props.text 

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

}
0x01 Mózg
źródło