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ść.

Tuomas Toivonen
źródło
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>
  }
}
polina-c
źródło
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

François Richard
źródło
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]);
petrichor
źródło
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?

Sujan Thakare
źródło
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}
         />
Nelles
źródło
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

tjr226
źródło
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.

JamesYin
źródło
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.

ibrahim alsimiry
źródło
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.

Indraneel Bende
źródło
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);
satyam soni
źródło
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);
Divyanshu Rawat
źródło
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

Rajesh Nasit
źródło