Reaguj: dlaczego komponent potomny nie aktualizuje się po zmianie właściwości


151

Dlaczego w poniższym przykładzie pseudokodu Child nie jest ponownie renderowany, gdy kontener zmienia foo.bar?

Container {
  handleEvent() {
    this.props.foo.bar = 123
  },

  render() {
    return <Child bar={this.props.foo.bar} />
}

Child {
  render() {
    return <div>{this.props.bar}</div>
  }
}

Nawet jeśli wywołam forceUpdate()po zmodyfikowaniu wartości w Container, Child nadal pokazuje starą wartość.


5
Czy to twój kod? Wygląda na to, że to nie jest prawidłowy kod React

Myślę, że wartość rekwizytów nie powinna się zmieniać w komponencie kontenera, zamiast tego powinna być zmieniana w komponencie nadrzędnym przez setState i ten stan powinien być mapowany na rekwizyty kontenerów
Piyush Patel

Użyj operatora rozprzestrzeniania w ten sposób <Child bar = {... this.props.foo.bar} />
Sourav Singh,

@AdrianWydmanski i pozostałe 5 osób, które poparły: en.wikipedia.org/wiki/Pseudocode
David Newcomb

Właściwości @PiyushPatel są aktualizowane, gdy komponent jest ponownie renderowany w miejscu, jak pokazuje przykład pseudokodu. Innym przykładem jest coś takiego jak użycie <Route exact path="/user/:email" component={ListUserMessagePage} />, łącze na tej samej stronie zaktualizuje właściwości bez tworzenia nowej instancji i uruchamiania zwykłych zdarzeń cyklu życia.
David Newcomb

Odpowiedzi:


115

Zaktualizuj element podrzędny, aby atrybut „klucz” był równy nazwie. Komponent będzie ponownie renderowany za każdym razem, gdy zmieni się klucz.

Child {
  render() {
    return <div key={this.props.bar}>{this.props.bar}</div>
  }
}

8
Dzięki za to. Korzystałem ze sklepu Redux i nie mogłem zmusić mojego komponentu do ponownego renderowania, dopóki nie dodałem klucza. (Użyłem biblioteki uuid do wygenerowania losowego klucza i zadziałało).
Maiya

6
tego też potrzebowałem. Jestem zdziwiony, kiedy jest to keywymagane, czy tylko setState? Mam dzieci, które polegają na stanie rodziców, ale nie uruchamiają ponownego renderowania ...
dcsan

3
Używałem indeksu jako klucza, ale nie działał on poprawnie. Teraz używam uuid i jest doskonały :) Dzięki @Maiya
Ali Rehman

1
@dcsan Myślę, że powodem jest to, że reakcja używa właściwości klucza pod maską, aby obliczyć, czy elementy renderowane dynamicznie przez mapę zostały przeniesione itp.
Maiya

1
samo wysłanie klucza jako rekwizytów w rodzicu również wykonało ponowne renderowanie w moim przypadku
zyrup

93

Ponieważ dzieci nie wyrzekają się, jeśli zmieniają się właściwości rodzica, ale jeśli zmienia się jego STAN :)

Pokazujesz to: https://facebook.github.io/react/tips/communicate-between-components.html

Będzie przekazywać dane od rodzica do dziecka za pomocą właściwości, ale nie ma tam logiki ponownego renderowania.

Musisz ustawić stan dla rodzica, a następnie oddać dziecko w stanie zmiany rodzica. To mogłoby pomóc. https://facebook.github.io/react/tips/expose-component-functions.html


7
Dlaczego jest tak, że setState spowoduje ponowne renderowanie, a forceUpdate nie? Również trochę poza tematem, ale w jaki sposób komponenty są aktualizowane, gdy rekwizyty są przekazywane z reduxu, a jego stan jest aktualizowany przez działanie?
Tuomas Toivonen

1
właściwości nie są aktualizowane, nawet jeśli zaktualizujesz komponent, przekazane właściwości nadal tam są. Flux to kolejny temat tak :)
François Richard

3
state! = props, musisz przeczytać więcej na ten temat. Nie dokonujesz aktualizacji za pomocą rekwizytów
François Richard

widać to bezpośrednio w kodzie źródłowym, to po prostu nie ten sam proces
Webwoman

więc to, co tu powiedziałeś, zostało zmienione lub nie zrozumiałem tego poprawnie, przy okazji zadałem pytanie, stackoverflow.com/questions/58903921/ ...
ILoveReactAndNode

62

Miałem ten sam problem. To jest moje rozwiązanie, nie jestem pewien, czy to dobra praktyka, powiedz mi, jeśli nie:

state = {
  value: this.props.value
};

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

UPD: Teraz możesz zrobić to samo używając React Hooks: (tylko jeśli komponent jest funkcją)

const [value, setValue] = useState(propName);
// This will launch only if propName value has chaged.
useEffect(() => { setValue(propName) }, [propName]);

3
To zdecydowanie poprawna odpowiedź, nie wspominając o najprostszej
Guilherme Ferreira

1
Ja też stosuję to podejście.
surowo

@ vancy-pants W componentDidUpdatetym przypadku znajduje się on w komponencie Child.
Nick Friskel

5
Nie potrzebujesz i nie powinieneś przekształcać właściwości do stanu w komponencie potomnym. Po prostu użyj rekwizytów bezpośrednio. W FunctionComponentsnim automatycznie zaktualizuje komponenty potomne, jeśli zmienią się właściwości.
oemera

1
To podejście działa również, gdy masz rekwizyty w komponentach potomnych pochodzących ze stanu nadrzędnego
Chefk5

12

Zgodnie z filozofią Reacta komponent nie może zmieniać swoich właściwości. powinny być otrzymane od rodzica i niezmienne. Tylko rodzic może zmieniać właściwości swoich dzieci.

ładne wyjaśnienie stanu kontra rekwizyty

przeczytaj też ten wątek Dlaczego nie mogę zaktualizować właściwości w pliku act.js?


Czy nie oznacza to również, że kontener nie może modyfikować rekwizytów, które otrzymuje ze sklepu Redux?
Tuomas Toivonen

3
Kontener nie otrzymuje rekwizytów bezpośrednio ze sklepu, są one dostarczane przez odczytanie części drzewa stanu redux (mylące ??). !! zamieszanie jest spowodowane tym, że nie wiemy o magicznej funkcji łączenia przez reakcję-redux. jeśli widzisz kod źródłowy metody connect, w zasadzie tworzy on komponent wyższego rzędu, który ma stan z właściwością storeState i jest zasubskrybowany do sklepu redux. gdy storeState się zmienia, następuje całe ponowne renderowanie, a zmodyfikowane właściwości są dostarczane do kontenera przez odczytanie zmienionego stanu. mam nadzieję, że to odpowiada na pytanie
Sujan Thakare

Dobre wyjaśnienie, myślenie o komponencie wyższego rzędu pomaga mi zrozumieć, co się dzieje
Tuomas Toivonen

8

Podczas tworzenia komponentów React z functionsi useState.

const [drawerState, setDrawerState] = useState(false);

const toggleDrawer = () => {
      // attempting to trigger re-render
      setDrawerState(!drawerState);
};

To nie działa

         <Drawer
            drawerState={drawerState}
            toggleDrawer={toggleDrawer}
         />

To działa (dodawanie klucza)

         <Drawer
            drawerState={drawerState}
            key={drawerState}
            toggleDrawer={toggleDrawer}
         />

7

Potwierdzono, dodanie klucza działa. Przeszedłem przez doktorów, żeby spróbować i zrozumieć dlaczego.

React chce wydajnie tworzyć komponenty potomne. Nie wyrenderuje nowego komponentu, jeśli jest taki sam jak inny element podrzędny, co przyspiesza ładowanie strony.

Dodanie klucza wymusza reakcję na renderowanie nowego komponentu, resetując w ten sposób stan tego nowego komponentu.

https://reactjs.org/docs/reconciliation.html#recursing-on-children


7

Powinieneś użyć setStatefunkcji. Jeśli nie, stan nie zapisze twojej zmiany, bez względu na to, jak użyjesz forceUpdate.

Container {
    handleEvent= () => { // use arrow function
        //this.props.foo.bar = 123
        //You should use setState to set value like this:
        this.setState({foo: {bar: 123}});
    };

    render() {
        return <Child bar={this.state.foo.bar} />
    }
    Child {
        render() {
            return <div>{this.props.bar}</div>
        }
    }
}

Twój kod wygląda na nieprawidłowy. Nie mogę przetestować tego kodu.


Nie powinno być <Child bar={this.state.foo.bar} />?
Josh Broadhurst

Dobrze. Aktualizuję odpowiedź. Ale jak powiedziałem, może to nie być prawidłowy kod. Po prostu skopiuj pytanie.
JamesYin,

2
export default function DataTable({ col, row }) {
  const [datatable, setDatatable] = React.useState({});
  useEffect(() => {
    setDatatable({
      columns: col,
      rows: row,
    });
  /// do any thing else 
  }, [row]);

  return (
    <MDBDataTableV5
      hover
      entriesOptions={[5, 20, 25]}
      entries={5}
      pagesAmount={4}
      data={datatable}
    />
  );
}

ten przykład służy useEffectdo zmiany stanu po propszmianie.


1

Użyj funkcji setState. Więc możesz to zrobić

       this.setState({this.state.foo.bar:123}) 

wewnątrz metody zdarzenia handle.

Po zaktualizowaniu stanu wyzwoli zmiany i nastąpi ponowne renderowanie.


1
Powinien to być this.setState ({foo.bar:123}), prawda?
Charith Jayasanka

To nawet nie zostanie skompilowane ⬆. Myślę, że miałeś na myśli: this.setState ({foo: {bar: 123}})
Avius

0

Prawdopodobnie powinieneś uczynić Child jako komponent funkcjonalny, jeśli nie zachowuje żadnego stanu i po prostu renderuje właściwości, a następnie wywołuje je z rodzica. Alternatywą jest użycie punktów zaczepienia z komponentem funkcjonalnym (useState), co spowoduje ponowne renderowanie komponentu bezstanowego.

Nie powinieneś także zmieniać propozycji, ponieważ są one niezmienne. Utrzymuj stan komponentu.

Child = ({bar}) => (bar);

0

Miałem ten sam problem. Miałem Tooltipkomponent, który otrzymywał showTooltippropozycję, który aktualizowałem na Parentpodstawie komponentu na podstawie ifwarunku, był aktualizowany w Parentkomponencie, ale Tooltipkomponent nie był renderowany.

const Parent = () => {
   let showTooltip = false;
   if(....){ showTooltip = true; }
   return(
      <Tooltip showTooltip={showTooltip}></Tooltip>
   )
}

Błąd, który popełniłem, to zadeklarowanie showTooltipjako let.

Zrozumiałem, co robię źle, naruszyłem zasady renderowania. Zastąpienie go hookami spełniło swoje zadanie.

const [showTooltip, setShowTooltip] =  React.useState<boolean>(false);

0

zdefiniuj zmienione właściwości w mapStateToProps metody connect w komponencie potomnym.

function mapStateToProps(state) {
  return {
    chanelList: state.messaging.chanelList,
  };
}

export default connect(mapStateToProps)(ChannelItem);

W moim przypadku kanał channelList jest aktualizowany, więc dodałem chanelList w mapStateToProps

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.