Wie kann ich das erneute Rendern von Komponenten mit Hooks in React erzwingen?

105

Betrachtet man unten das Hakenbeispiel

   import { useState } from 'react';

   function Example() {
       const [count, setCount] = useState(0);

       return (
           <div>
               <p>You clicked {count} times</p>
               <button onClick={() => setCount(count + 1)}>
                  Click me
               </button>
          </div>
        );
     }

Grundsätzlich verwenden wir die Methode this.forceUpdate (), um zu erzwingen, dass die Komponente in React-Klassenkomponenten wie im folgenden Beispiel sofort neu gerendert wird

    class Test extends Component{
        constructor(props){
             super(props);
             this.state = {
                 count:0,
                 count2: 100
             }
             this.setCount = this.setCount.bind(this);//how can I do this with hooks in functional component 
        }
        setCount(){
              let count = this.state.count;
                   count = count+1;
              let count2 = this.state.count2;
                   count2 = count2+1;
              this.setState({count});
              this.forceUpdate();
              //before below setState the component will re-render immediately when this.forceUpdate() is called
              this.setState({count2: count
        }

        render(){
              return (<div>
                   <span>Count: {this.state.count}></span>. 
                   <button onClick={this.setCount}></button>
                 </div>
        }
 }

Meine Frage lautet jedoch: Wie kann ich die oben genannte Funktionskomponente zwingen, sofort mit Hooks neu zu rendern?

Hemadri Dasari
quelle
1
Können Sie eine Version Ihrer Originalkomponente veröffentlichen, die das verwendet this.forceUpdate()? Vielleicht gibt es einen Weg, dasselbe ohne das zu erreichen.
Jacob
Die letzte Zeile in setCount wird abgeschnitten. Es ist unklar, wozu setCount im aktuellen Zustand dient.
Estus Flask
Das ist nur eine Aktion nach this.forceUpdate (); Ich habe das hinzugefügt, um dies zu erklären.forceUpdate () in meiner Frage
Hemadri Dasari
Für das, was es wert ist: Ich habe damit gerungen, weil ich dachte, ich müsste manuell neu gerendert werden, und schließlich wurde mir klar, dass ich einfach eine extern gehaltene Variable in einen Status-Hook verschieben und die Einstellungsfunktion nutzen musste, ohne die alle meine Probleme behoben wurden ein erneutes Rendern. Um nicht zu sagen, dass es nie benötigt wird, aber es lohnt sich, einen dritten und vierten Blick darauf zu werfen, ob es in Ihrem speziellen Anwendungsfall tatsächlich benötigt wird.
Alexander Nied

Antworten:

73

Dies ist möglich , mit useStateoder useReducer, da useStateAnwendungen useReducerintern :

const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);

forceUpdateist nicht für den normalen Gebrauch vorgesehen, nur für Tests oder andere offene Fälle. Diese Situation kann auf konventionellere Weise angegangen werden.

setCountist ein Beispiel für eine nicht ordnungsgemäße Verwendung forceUpdate, setStateist aus Leistungsgründen asynchron und sollte nicht zur Synchronisierung gezwungen werden, nur weil Statusaktualisierungen nicht korrekt durchgeführt wurden. Wenn sich ein Status auf den zuvor festgelegten Status stützt, sollte dies mit der Updater-Funktion erfolgen .

Wenn Sie den Status basierend auf dem vorherigen Status festlegen müssen, lesen Sie das unten stehende Updater-Argument.

<...>

Sowohl der Status als auch die von der Updater-Funktion empfangenen Requisiten sind garantiert auf dem neuesten Stand. Die Ausgabe des Updaters wird flach mit dem Status zusammengeführt.

setCount Dies ist möglicherweise kein anschauliches Beispiel, da sein Zweck unklar ist. Dies gilt jedoch für die Updater-Funktion:

setCount(){
  this.setState(({count}) => ({ count: count + 1 }));
  this.setState(({count2}) => ({ count2: count + 1 }));
  this.setState(({count}) => ({ count2: count + 1 }));
}

Dies wird 1: 1 in Hooks übersetzt, mit der Ausnahme, dass Funktionen, die als Rückrufe verwendet werden, besser gespeichert werden sollten:

   const [state, setState] = useState({ count: 0, count2: 100 });

   const setCount = useCallback(() => {
     setState(({count}) => ({ count: count + 1 }));
     setState(({count2}) => ({ count2: count + 1 }));
     setState(({count}) => ({ count2: count + 1 }));
   }, []);
Estus Flask
quelle
Wie funktioniert das const forceUpdate = useCallback(() => updateState({}), []);? Erzwingt es überhaupt ein Update?
Dávid Molnár
1
@ DávidMolnár useCallbackmerkt sich forceUpdate, so dass es während der Lebensdauer der Komponenten konstant bleibt und sicher als Requisite übergeben werden kann. updateState({})Aktualisiert den Status bei jedem forceUpdateAufruf mit einem neuen Objekt . Dies führt zu einem erneuten Rendern. Also ja, es erzwingt ein Update, wenn es aufgerufen wird.
Estus Flask
3
Das useCallbackTeil ist also nicht wirklich notwendig. Es sollte ohne es gut funktionieren.
Dávid Molnár
Und es würde nicht funktionieren, updateState(0)weil 0es sich um einen primitiven Datentyp handelt? Muss es ein Objekt sein oder würde updateState([])(dh die Verwendung mit einem Array) auch funktionieren?
Andru
1
@Andru Ja, ein Status wird einmal aktualisiert, weil 0 === 0. Ja, ein Array funktioniert, weil es auch ein Objekt ist. Alles, was die Gleichheitsprüfung nicht besteht, kann wie updateState(Math.random())ein Zähler verwendet werden.
Estus Flask
25

Wie die anderen bereits erwähnt haben, useStatefunktioniert - so implementiert mobx-react-lite Updates - können Sie etwas Ähnliches tun.

Definieren Sie einen neuen Haken, useForceUpdate-

import { useState, useCallback } from 'react'

export function useForceUpdate() {
  const [, setTick] = useState(0);
  const update = useCallback(() => {
    setTick(tick => tick + 1);
  }, [])
  return update;
}

und verwenden Sie es in einer Komponente -

const forceUpdate = useForceUpdate();
if (...) {
  forceUpdate(); // force re-render
}

Siehe https://github.com/mobxjs/mobx-react-lite/blob/master/src/utils.ts und https://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver .ts

Brian Burns
quelle
2
useForceUpdateSoweit ich von Hooks weiß, funktioniert dies möglicherweise nicht, da bei jedem erneuten Rendern der Funktion eine neue Funktion zurückgegeben wird. Damit forceUpdatees funktioniert, wenn es in a verwendet wird useEffect, sollte es zurückgegeben werden. useCallback(update)Siehe kentcdodds.com/blog/usememo-and-usecallback
Martin Ratinaud
Danke, @MartinRatinaud - ja, es könnte einen Speicherverlust ohne useCallback (?) Verursachen - behoben.
Brian Burns
22

Im Allgemeinen können Sie jeden Statusbehandlungsansatz verwenden, mit dem Sie ein Update auslösen möchten.

Mit TypeScript

Code und Box Beispiel

useState

const forceUpdate: () => void = React.useState()[1].bind(null, {})  // see NOTE below

useReducer

const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void

als benutzerdefinierter Haken

Wickeln Sie einfach den von Ihnen bevorzugten Ansatz so ein

function useForceUpdate(): () => void {
  return React.useReducer(() => ({}), {})[1] as () => void // <- paste here
}

Wie funktioniert das?

" Ein Update auslösen " bedeutet, der React Engine mitzuteilen, dass sich ein Wert geändert hat und dass Ihre Komponente erneut gerendert werden soll.

[, setState]from useState()erfordert einen Parameter. Wir werden es los, indem wir ein neues Objekt binden {}.
() => ({})in useReducerist ein Dummy-Reduzierer, der jedes Mal, wenn eine Aktion ausgelöst wird, ein neues Objekt zurückgibt.
{} (frisches Objekt) ist erforderlich, damit eine Aktualisierung durch Ändern einer Referenz im Status ausgelöst wird.

PS: useStateWraps nur useReducerintern. Quelle

HINWEIS: Die Verwendung von .bind mit useState führt zu einer Änderung der Funktionsreferenz zwischen den Renderings. Es ist möglich, es in useCallback zu verpacken, wie hier bereits erklärt , aber dann wäre es kein sexy Einzeiler . Die Reducer-Version behält bereits die Referenzgleichheit zwischen Renderings bei. Dies ist wichtig, wenn Sie die forceUpdate-Funktion in Requisiten übergeben möchten.

einfache JS

const forceUpdate = React.useState()[1].bind(null, {})  // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]
Qwerty
quelle
14

Alternative zu @ MinhKhas Antwort:

Es kann viel sauberer sein mit useReducer:

const [, forceUpdate] = useReducer(x => x + 1, 0);

Verwendung: forceUpdate()- Reiniger ohne Parameter

nullhook
quelle
12

Sie können den useState einfach so definieren:

const [, forceUpdate] = React.useState(0);

Und Verwendung: forceUpdate(n => !n)

Ich hoffe das hilft !

Minh Kha
quelle
7
Schlägt fehl, wenn forceUpdate gerade einmal pro Render aufgerufen wird.
Izhaki
Erhöhen Sie einfach den Wert weiter.
Gary
2
Dies ist fehleranfällig und sollte entfernt oder bearbeitet werden.
Slikts
10

Sie sollten Ihre Komponente vorzugsweise nur vom Status und den Requisiten abhängig machen, damit sie wie erwartet funktioniert. Wenn Sie jedoch wirklich eine Funktion benötigen, um das erneute Rendern der Komponente zu erzwingen, können Sie den useStateHook verwenden und die Funktion bei Bedarf aufrufen.

Beispiel

const { useState, useEffect } = React;

function Foo() {
  const [, forceUpdate] = useState();

  useEffect(() => {
    setTimeout(forceUpdate, 2000);
  }, []);

  return <div>{Date.now()}</div>;
}

ReactDOM.render(<Foo />, document.getElementById("root"));
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>

<div id="root"></div>

Tholle
quelle
Ok, aber warum hat React this.forceUpdate () eingeführt? an erster Stelle, wenn die Komponente in früheren Versionen mit setState neu gerendert wird?
Hemadri Dasari
1
@ Think-Twice Ich persönlich habe es noch nie benutzt, und ich kann mir momentan keinen guten Anwendungsfall dafür vorstellen, aber ich denke, es ist eine Notluke für diese wirklich speziellen Anwendungsfälle. "Normalerweise sollten Sie versuchen, alle Verwendungen von forceUpdate()und nur von this.propsund this.statein zu vermeiden render()."
Tholle
1
Einverstanden. Ich habe das in meiner Erfahrung noch nie benutzt, aber ich weiß, wie es funktioniert, also versuche ich nur zu verstehen, wie das Gleiche mit Haken gemacht werden kann
Hemadri Dasari
1
@Tholle and I can't think of a good use case for it right now Ich habe eine, wie wäre es, wenn der Status nicht von React gesteuert wird. Ich benutze Redux nicht, aber ich gehe davon aus, dass es eine Art Forceupdate durchführen muss. Ich persönlich verwende Proxys, um den Status beizubehalten. Die Komponente kann dann nach Änderungen an den Requisiten suchen und diese dann aktualisieren. Scheint auch sehr effizient zu arbeiten. Beispielsweise werden alle meine Komponenten dann von einem Proxy gesteuert. Dieser Proxy wird von SessionStorage unterstützt, sodass selbst wenn der Benutzer seine Webseite aktualisiert, der Status gleichmäßiger Dropdowns usw. beibehalten wird. IOW: Ich benutze überhaupt keinen Status, alles wird mit Requisiten gesteuert.
Keith
8

React Hooks FAQ offizielle Lösung für forceUpdate:

const [_, forceUpdate] = useReducer((x) => x + 1, 0);
// usage
<button onClick={forceUpdate}>Force update</button>

Arbeitsbeispiel

ford04
quelle
6

Einfacher Code

const forceUpdate = React.useReducer(bool => !bool)[1];

Verwenden:

forceUpdate();
GilCarvalhoDev
quelle
5

Sie können (ab) normale Hooks verwenden, um ein erneutes Rendern zu erzwingen, indem Sie die Tatsache ausnutzen, dass React keine Booleschen Werte in JSX-Code druckt

// create a hook
const [forceRerender, setForceRerender] = React.useState(true);

// ...put this line where you want to force a rerender
setForceRerender(!forceRerender);

// ...make sure that {forceRerender} is "visible" in your js code
// ({forceRerender} will not actually be visible since booleans are
// not printed, but updating its value will nonetheless force a
// rerender)
return (
  <div>{forceRerender}</div>
)

Fergie
quelle
1
In diesem Fall erkennt React.useEffect das Update möglicherweise nicht erneut, wenn setBoolean zweimal geändert wird.
Alma
Soweit ich weiß, behandelt React jedes boolesche Update als Grund, die Seite erneut zu rendern, auch wenn der boolesche Wert schnell wieder zurückgeschaltet wird. Das heißt, React ist natürlich kein Standard, und genau wie es in diesem Fall funktioniert, ist undefiniert und kann sich ändern.
Fergie
1
Ich weiß es nicht. Aus irgendeinem Grund bin ich von dieser Version angezogen. Es kitzelt mich :-). Außerdem fühlt es sich rein an. Es hat sich etwas geändert, von dem mein JSX abhängt, also rendere ich erneut. Die Unsichtbarkeit beeinträchtigt diese IMO nicht.
Adrian Bartholomew
4

Mögliche Option ist, die Aktualisierung nur für bestimmte Komponenten zu erzwingen key. Das Aktualisieren des Schlüssels löst ein Rendering der Komponente aus (die zuvor nicht aktualisiert werden konnte).

Zum Beispiel:

const [tableKey, setTableKey] = useState(1);
...

useEffect(() => {
    ...
    setTableKey(tableKey + 1);
}, [tableData]);

...
<DataTable
    key={tableKey}
    data={tableData}/>
Idan
quelle
1
Dies ist häufig der sauberste Weg, wenn eine 1: 1-Beziehung zwischen einem Statuswert und der Anforderung zum erneuten Rendern besteht.
Jaimefps
1
Dies ist eine einfache Lösung
Priyanka V
3

Einzeilige Lösung:

const useForceUpdate = () => useState()[1];

useState gibt ein Wertepaar zurück: den aktuellen Status und eine Funktion, die ihn aktualisiert - Status und Setter . Hier verwenden wir nur den Setter, um ein erneutes Rendern zu erzwingen.

Arielhad
quelle
2

Meine Variation von forceUpdateerfolgt nicht über ein counter, sondern über ein Objekt:

// Emulates `forceUpdate()`
const [unusedState, setUnusedState] = useState()
const forceUpdate = useCallback(() => setUnusedState({}), [])

Weil {} !== {}jedes Mal.

Katamphetamin
quelle
Was ist useCallback()? Woher kommt das? Hoppla. Ich sehe es jetzt ...
zipzit
2

Dadurch werden abhängige Komponenten dreimal gerendert (Arrays mit gleichen Elementen sind nicht gleich):

const [msg, setMsg] = useState([""])

setMsg(["test"])
setMsg(["test"])
setMsg(["test"])
Janek Olszak
quelle
1
Ich glaube, Sie müssen nicht einmal einen Gegenstand in das Array einfügen. Ein leeres Array entspricht nicht genau einem anderen leeren Array, nur durch eine andere Referenz, genau wie Objekte.
Qwerty
Ja, ich wollte das nur zeigen, um Daten
weiterzugeben
1

Informationen zu regulären Komponenten, die auf React Class basieren, finden Sie unter React Docs für die forceUpdateAPI unter dieser URL. Die Dokumente erwähnen Folgendes:

Normalerweise sollten Sie versuchen, alle Verwendungen von forceUpdate () zu vermeiden und nur aus this.props und this.state in render () zu lesen.

In den Dokumenten wird jedoch auch erwähnt, dass:

Wenn Ihre render () -Methode von anderen Daten abhängt, können Sie React mitteilen, dass die Komponente erneut gerendert werden muss, indem Sie forceUpdate () aufrufen.

Obwohl Anwendungsfälle für die Verwendung forceUpdateselten sind und ich sie noch nie verwendet habe, habe ich gesehen, dass sie von anderen Entwicklern in einigen älteren Unternehmensprojekten verwendet wurden, an denen ich gearbeitet habe.

Informationen zu den entsprechenden Funktionen für Funktionskomponenten finden Sie in den React Docs for HOOKS unter dieser URL. Gemäß der obigen URL kann der Hook "useReducer" verwendet werden, um eine forceUpdateFunktionalität für Funktionskomponenten bereitzustellen .

Im that does not use state or propsFolgenden finden Sie ein funktionierendes Codebeispiel , das auch in CodeSandbox unter dieser URL verfügbar ist

import React, { useReducer, useRef } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  // Use the useRef hook to store a mutable value inside a functional component for the counter
  let countref = useRef(0);

  const [, forceUpdate] = useReducer(x => x + 1, 0);

  function handleClick() {
    countref.current++;
    console.log("Count = ", countref.current);
    forceUpdate(); // If you comment this out, the date and count in the screen will not be updated
  }

  return (
    <div className="App">
      <h1> {new Date().toLocaleString()} </h1>
      <h2>You clicked {countref.current} times</h2>
      <button
        onClick={() => {
          handleClick();
        }}
      >
        ClickToUpdateDateAndCount
      </button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

HINWEIS: Unter dieser URL ist auch ein alternativer Ansatz mit dem useState-Hook (anstelle von useReducer) verfügbar .

Alan CS
quelle
1

react-tidyhat einen benutzerdefinierten Haken nur dafür useRefresh:

import React from 'react'
import {useRefresh} from 'react-tidy'

function App() {
  const refresh = useRefresh()
  return (
    <p>
      The time is {new Date()} <button onClick={refresh}>Refresh</button>
    </p>
  )
}

Erfahren Sie mehr über diesen Haken

Haftungsausschluss Ich bin der Autor dieser Bibliothek.

webNeat
quelle