Pouvez-vous forcer un composant React à se restituer sans appeler setState?

690

J'ai un objet observable externe (au composant) sur lequel je veux écouter les changements. Lorsque l'objet est mis à jour, il émet des événements de modification, puis je souhaite restituer le composant lorsqu'une modification est détectée.

Avec un niveau supérieur, React.rendercela a été possible, mais au sein d'un composant, cela ne fonctionne pas (ce qui est logique puisque la renderméthode renvoie simplement un objet).

Voici un exemple de code:

export default class MyComponent extends React.Component {

  handleButtonClick() {
    this.render();
  }

  render() {
    return (
      <div>
        {Math.random()}
        <button onClick={this.handleButtonClick.bind(this)}>
          Click me
        </button>
      </div>
    )
  }
}

Cliquer sur le bouton appelle en interne this.render(), mais ce n'est pas ce qui provoque réellement le rendu (vous pouvez le voir en action car le texte créé par {Math.random()}ne change pas). Cependant, si j'appelle simplement this.setState()au lieu de this.render(), cela fonctionne très bien.

Je suppose donc que ma question est la suivante: les composants React doivent-ils avoir un état pour pouvoir être rendus? Existe-t-il un moyen de forcer le composant à se mettre à jour à la demande sans changer l'état?

Philip Walton
la source
7
la réponse acceptée dit que this.forceUpdate()c'est la bonne solution alors que le reste de toutes les réponses et plusieurs commentaires sont contre l'utilisation forceUpdate(). Serait-il alors bon de dire que la question n'a pas encore obtenu une solution / réponse appropriée?
adi
7
La réponse exceptée a répondu à ma question à l'époque. C'est techniquement la réponse que je cherchais, et je pense toujours que la bonne réponse. Les autres réponses, je pense, sont de bonnes informations supplémentaires pour les personnes ayant la même question à connaître.
Philip Walton
Il est intéressant de noter que vous n'avez besoin de rien d'autre que de l'initialiser en un objet simple, puis d'appeler this.setState ({}) déclenche simplement un nouveau rendu. Réagir est super mais parfois aussi bizarre. Par conséquent, vous pouvez directement parcourir les données d'un magasin lors du déclenchement d'une modification sans plomberie supplémentaire ou vous soucier des données par instance de composant.
Jason Sebring
Dans l'ensemble, je dirais oui. Si vous utilisez la mise à jour forcée, c'est pour mettre à jour les composants où ils peuvent dépendre de modifications en dehors de la gestion de l'état de votre application. Je ne peux pas penser à un bon exemple de cela. Utile à savoir cependant.
Daniel

Réponses:

765

Dans votre composant, vous pouvez appeler this.forceUpdate()pour forcer un nouveau rendu.

Documentation: https://facebook.github.io/react/docs/component-api.html

Crob
la source
170
Une autre façon est this.setState (this.state);
kar
25
L'utilisation de forceupdate est déconseillée, veuillez consulter la dernière réponse.
Varand Pezeshkian
42
Bien que ce this.forceUpdate()ne soit pas une très bonne solution au problème des demandeurs, c'est la bonne réponse à la question posée dans le titre. Bien qu'il soit généralement à éviter, il existe des situations où forceUpdate est une bonne solution.
ArneHugo
8
@kar En fait, une logique supplémentaire peut être implémentée pour shouldComponentUpdate()rendre votre solution futile.
Qwerty
4
Dépassement de la
326

forceUpdatedoit être évité car il s'écarte d'un état d'esprit React. Les documents React citent un exemple de cas d' forceUpdateutilisation:

Par défaut, lorsque l'état ou les accessoires de votre composant changent, votre composant sera rendu à nouveau. Cependant, si ceux-ci changent implicitement (par exemple: les données profondément dans un objet changent sans changer l'objet lui-même) ou si votre méthode render () dépend de certaines autres données, vous pouvez dire à React qu'il doit réexécuter render () en appelant Forcer la mise à jour().

Cependant, je voudrais proposer l'idée que même avec des objets profondément imbriqués, ce forceUpdaten'est pas nécessaire. En utilisant une source de données immuable, le suivi des modifications devient bon marché; un changement se traduira toujours par un nouvel objet, il nous suffit donc de vérifier si la référence à l'objet a changé. Vous pouvez utiliser la bibliothèque Immutable JS pour implémenter des objets de données immuables dans votre application.

Normalement, vous devriez essayer d'éviter toutes les utilisations de forceUpdate () et lire uniquement à partir de this.props et this.state dans render (). Cela rend votre composant "pur" et votre application beaucoup plus simple et plus efficace. Forcer la mise à jour()

La modification de la clé de l'élément que vous souhaitez restituer fonctionnera. Définissez l'accessoire de clé sur votre élément via l'état, puis lorsque vous souhaitez mettre à jour l'état d'ensemble pour avoir une nouvelle clé.

<Element key={this.state.key} /> 

Ensuite, un changement se produit et vous réinitialisez la clé

this.setState({ key: Math.random() });

Je veux noter que cela remplacera l'élément sur lequel la clé change. Un exemple où cela pourrait être utile est lorsque vous avez un champ de saisie de fichier que vous souhaitez réinitialiser après un téléchargement d'image.

Bien que la vraie réponse à la question du PO soit, forceUpdate()j'ai trouvé cette solution utile dans différentes situations. Je tiens également à noter que si vous vous retrouvez à utiliser, forceUpdatevous voudrez peut-être revoir votre code et voir s'il existe une autre façon de faire les choses.

NOTE 1-9-2019:

Ce qui précède (changer la clé) remplacera complètement l'élément. Si vous vous trouvez à mettre à jour la clé pour apporter des modifications, vous avez probablement un problème ailleurs dans votre code. L'utilisation Math.random()de la clé recréera l'élément avec chaque rendu. Je ne recommanderais PAS de mettre à jour la clé comme ceci car react utilise la clé pour déterminer la meilleure façon de restituer les choses.

pizzarob
la source
16
Je serais intéressé de savoir pourquoi cela a obtenu un downvote? Je me suis cogné la tête en essayant d'obtenir des lecteurs d'écran pour répondre aux alertes aria et c'est la seule technique que j'ai trouvée qui fonctionne de manière fiable. Si vous avez quelque chose dans votre interface utilisateur qui génère la même alerte à chaque fois qu'il est cliqué, par défaut, react ne restitue pas le DOM afin que le lecteur d'écran n'annonce pas le changement. La définition d'une clé unique le fait fonctionner. Peut-être que le downvote était parce qu'il implique toujours de définir l'état. Mais forcer un nouveau rendu sur DOM en définissant la clé est d'or!
Martin Bayly
2
J'ai beaucoup aimé cette réponse car - 1: l'utilisation de forceupdate () est déconseillée, et 2: cette façon est très utile pour les composants tiers que vous avez installés via npm, ce qui ne signifie pas vos propres composants. Par exemple, React-resizable-and-movable est un composant tiers que j'utilise, mais il ne réagit pas à mon setstate de composant. c'est une excellente solution.
Varand Pezeshkian
81
C'est un hack et un abus de key. Premièrement, son intention n'est pas claire. Un lecteur de votre code devra travailler dur pour comprendre pourquoi vous utilisez keycette méthode. Deuxièmement, ce n'est pas plus pur que forceUpdate. "Pure" React signifie que la présentation visuelle de votre composant dépend à 100% de son état, donc si vous changez un état, il se met à jour. Si cependant, vous avez des objets profondément imbriqués (le scénario les forceUpdatedocuments citent une raison de l'utiliser), alors utiliser le forceUpdaterend clair. Troisièmement, Math.random()c'est… aléatoire. Théoriquement, il pourrait générer le même nombre aléatoire.
atomkirk
8
@xtraSimplicity Je ne peux pas accepter que cela soit conforme à la façon de faire de React. forceUpdateest la façon de réagir de React.
Rob Grant
3
@Robert Grant, avec le recul, je suis d'accord. La complexité supplémentaire n'en vaut pas vraiment la peine.
XtraSimplicity
80

En fait, forceUpdate()c'est la seule solution correcte, car elle setState()pourrait ne pas déclencher un nouveau rendu si une logique supplémentaire est implémentée dans shouldComponentUpdate()ou lorsqu'elle revient simplement false.

Forcer la mise à jour()

L'appel forceUpdate()provoquera render()un appel sur le composant, en sautant shouldComponentUpdate(). plus...

setState ()

setState()déclenchera toujours un nouveau rendu à moins que la logique de rendu conditionnel ne soit implémentée dans shouldComponentUpdate(). plus...


forceUpdate() peut être appelé depuis votre composant par this.forceUpdate()


Hooks: Comment puis-je forcer le rendu des composants avec des hooks dans React?


BTW: Êtes-vous en train de muter ou vos propriétés imbriquées ne se propagent-elles pas?

Qwerty
la source
1
une chose intéressante à noter est que les états d'état en dehors de setState tels que this.state.foo = 'bar' ne déclencheront pas la méthode de cycle de vie de rendu
lfender6445
4
@ lfender6445 L'affectation directe de l'état à l' this.stateextérieur du constructeur devrait également générer une erreur. Pourrait ne pas l'avoir fait en 2016; mais je suis quasiment sûr qu'il le fait maintenant.
Tyler
J'ai ajouté quelques liens pour contourner les affectations directes d'état.
Qwerty
36

J'ai évité forceUpdateen suivant

MAL : n'utilisez pas l'index comme clé

this.state.rows.map((item, index) =>
   <MyComponent cell={item} key={index} />
)

FAÇON CORRECTE : Utilisez l'ID de données comme clé, cela peut être un guide, etc.

this.state.rows.map((item) =>
   <MyComponent item={item} key={item.id} />
)

donc en faisant une telle amélioration de code, votre composant sera UNIQUE et restituera naturellement

vijay
la source
this.state.rows.map((item) => <MyComponent item={item} key={item.id} /> )
Tal
Merci beaucoup de m'avoir poussé dans la bonne direction. L'utilisation de l'index comme clé était en effet mon problème.
RoiEX
Pour justifier mon vote à la baisse, bien qu'il soit judicieux d'utiliser cette approche, cette réponse n'est pas liée à la question.
micnic
34

Lorsque vous souhaitez que deux composants React communiquent, qui ne sont pas liés par une relation (parent-enfant), il est conseillé d'utiliser Flux ou des architectures similaires.

Ce que vous voulez faire, c'est écouter les modifications du magasin de composants observable , qui contient le modèle et son interface, et enregistrer les données qui provoquent le changement du rendu comme statedans MyComponent. Lorsque le magasin envoie les nouvelles données, vous modifiez l'état de votre composant, ce qui déclenche automatiquement le rendu.

Normalement, vous devriez essayer d'éviter d'utiliser forceUpdate(). De la documentation:

Normalement, vous devriez essayer d'éviter toutes les utilisations de forceUpdate () et lire uniquement à partir de this.props et this.state dans render (). Cela rend votre application beaucoup plus simple et plus efficace

gcedo
la source
2
Quel est l'état de la mémoire d'un état de composant? Si j'ai cinq composants sur une page et qu'ils écoutent tous un seul magasin, ai-je les données cinq fois en mémoire ou sont-elles des références? Et tous ces auditeurs ne s'additionnent pas rapidement? Pourquoi est-il préférable de «couler» que de simplement transmettre des données à votre cible?
AJFarkas
5
En fait, les recommandations sont de transmettre les données du magasin aux accessoires de composant et d'utiliser uniquement l'état du composant pour des choses comme l'état de défilement et d'autres choses spécifiques à l'interface utilisateur. Si vous utilisez redux (je le recommande), vous pouvez utiliser la fonction de connexion de react-reduxpour mapper automatiquement l'état du magasin aux accessoires de composant chaque fois que nécessaire en fonction d'une fonction de mappage que vous fournissez.
Stijn de Witt
1
@AJFarkas, l'état serait affecté aux données d'un magasin qui ne sont de toute façon qu'un pointeur, donc la mémoire n'est pas un problème à moins que vous ne cloniez.
Jason Sebring
4
"forceUpdate () doit toujours être évité" est incorrect. La documentation indique clairement: "Normalement, vous devriez essayer d'éviter toutes les utilisations de forceUpdate ()". Il existe des cas d'utilisation valides deforceUpdate
AJP
2
@AJP vous avez absolument raison, c'était un parti pris personnel parlant :) J'ai édité la réponse.
gcedo
18

utiliser des crochets ou HOC faire votre choix

À l'aide de crochets ou du modèle HOC (composant d'ordre supérieur) , vous pouvez avoir des mises à jour automatiques lorsque vos magasins changent. Il s'agit d'une approche très légère sans cadre.

useStore Hooks façon de gérer les mises à jour du magasin

interface ISimpleStore {
  on: (ev: string, fn: () => void) => void;
  off: (ev: string, fn: () => void) => void;
}

export default function useStore<T extends ISimpleStore>(store: T) {
  const [storeState, setStoreState] = useState({store});
  useEffect(() => {
    const onChange = () => {
      setStoreState({store});
    }
    store.on('change', onChange);
    return () => {
      store.off('change', onChange);
    }
  }, []);
  return storeState.store;
}

withStores HOC gère les mises à jour du magasin

export default function (...stores: SimpleStore[]) {
  return function (WrappedComponent: React.ComponentType<any>) {
    return class WithStore extends PureComponent<{}, {lastUpdated: number}> {
      constructor(props: React.ComponentProps<any>) {
        super(props);
        this.state = {
          lastUpdated: Date.now(),
        };
        this.stores = stores;
      }

      private stores?: SimpleStore[];

      private onChange = () => {
        this.setState({lastUpdated: Date.now()});
      };

      componentDidMount = () => {
        this.stores &&
          this.stores.forEach((store) => {
            // each store has a common change event to subscribe to
            store.on('change', this.onChange);
          });
      };

      componentWillUnmount = () => {
        this.stores &&
          this.stores.forEach((store) => {
            store.off('change', this.onChange);
          });
      };

      render() {
        return (
          <WrappedComponent
            lastUpdated={this.state.lastUpdated}
            {...this.props}
          />
        );
      }
    };
  };
}

Classe SimpleStore

import AsyncStorage from '@react-native-community/async-storage';
import ee, {Emitter} from 'event-emitter';

interface SimpleStoreArgs {
  key?: string;
  defaultState?: {[key: string]: any};
}

export default class SimpleStore {
  constructor({key, defaultState}: SimpleStoreArgs) {
    if (key) {
      this.key = key;
      // hydrate here if you want w/ localState or AsyncStorage
    }
    if (defaultState) {
      this._state = {...defaultState, loaded: false};
    } else {
      this._state = {loaded: true};
    }
  }
  protected key: string = '';
  protected _state: {[key: string]: any} = {};
  protected eventEmitter: Emitter = ee({});
  public setState(newState: {[key: string]: any}) {
    this._state = {...this._state, ...newState};
    this.eventEmitter.emit('change');
    if (this.key) {
      // store on client w/ localState or AsyncStorage
    }
  }
  public get state() {
    return this._state;
  }
  public on(ev: string, fn:() => void) {
    this.eventEmitter.on(ev, fn);
  }
  public off(ev: string, fn:() => void) {
    this.eventEmitter.off(ev, fn);
  }
  public get loaded(): boolean {
    return !!this._state.loaded;
  }
}

Comment utiliser

Dans le cas des crochets:

// use inside function like so
const someState = useStore(myStore);
someState.myProp = 'something';

Dans le cas de HOC:

// inside your code get/set your store and stuff just updates
const val = myStore.myProp;
myOtherStore.myProp = 'something';
// return your wrapped component like so
export default withStores(myStore)(MyComponent);

ASSUREZ-VOUS D'exporter vos magasins en tant que singleton pour bénéficier du changement global comme ceci:

class MyStore extends SimpleStore {
  public get someProp() {
    return this._state.someProp || '';
  }
  public set someProp(value: string) {
    this.setState({...this._state, someProp: value});
  }
}
// this is a singleton
const myStore = new MyStore();
export {myStore};

Cette approche est assez simple et fonctionne pour moi. Je travaille également dans de grandes équipes et j'utilise Redux et MobX et je trouve que celles-ci sont également bonnes, mais juste beaucoup de passe-partout. Personnellement, j'aime ma propre approche parce que j'ai toujours détesté beaucoup de code pour quelque chose qui peut être simple quand vous en avez besoin.

Jason Sebring
la source
2
Je pense qu'il ya une faute de frappe dans votre exemple de classe magasin dans la méthode de mise à jour d' avoir à écrire la première ligne de la méthode comme suit: this._data = {...this._data, ...newData}.
Norbert
2
React décourage l'héritage en faveur de la composition. reactjs.org/docs/composition-vs-inheritance.html
Fred
1
Vous vous interrogez simplement sur la clarté / lisibilité, dans quelle mesure this.setState (this.state) peut-il être meilleur que this.forceUpdate ()?
Zoman
17

Je suppose donc que ma question est la suivante: les composants React doivent-ils avoir un état pour pouvoir être rendus à nouveau? Existe-t-il un moyen de forcer le composant à se mettre à jour à la demande sans changer l'état?

Les autres réponses ont essayé d'illustrer comment vous pouviez, mais le fait est que vous ne devriez pas . Même la solution hacky de changer la clé manque le point. La puissance de React consiste à abandonner le contrôle de la gestion manuelle du moment où quelque chose doit être rendu, et au lieu de simplement se préoccuper de la façon dont quelque chose doit être mappé sur les entrées. Ensuite, fournissez un flux d'entrées.

Si vous devez forcer manuellement le re-rendu, vous ne faites certainement pas quelque chose de bien.

Kyle Baker
la source
@ art-solopov, de quel problème parlez-vous? Quels documents les présentent? Et en supposant que vous vous référez à des objets imbriqués dans l'état, la réponse est d'utiliser une structure différente pour stocker votre état. C'est clairement une façon sous-optimale de gérer l'état dans React. Vous ne devez pas non plus gérer l'état séparément. Vous perdez l'un des plus grands avantages de React si vous ne travaillez pas avec le système de gestion d'état. La gestion manuelle des mises à jour est un anti-modèle.
Kyle Baker
S'il vous plaît, pouvez-vous vérifier cette question stackoverflow.com/questions/61277292/… ?
HuLu ViCa
4

Vous pouvez le faire de deux manières:

1. Utilisez la forceUpdate()méthode:

Il y a quelques problèmes qui peuvent survenir lors de l'utilisation de la forceUpdate()méthode. Un exemple est qu'il ignore la shouldComponentUpdate()méthode et restitue la vue indépendamment du fait qu'elle shouldComponentUpdate()retourne false. Pour cette raison, l'utilisation de forceUpdate () doit être évitée autant que possible.

2. Passer this.state à la setState()méthode

La ligne de code suivante résout le problème de l'exemple précédent:

this.setState(this.state);

Vraiment, tout cela ne fait que remplacer l'état actuel par l'état actuel qui déclenche un nouveau rendu. Ce n'est toujours pas nécessairement la meilleure façon de faire les choses, mais cela résout certains des problèmes que vous pourriez rencontrer en utilisant la méthode forceUpdate ().

kojow7
la source
2
Étant donné que setState est groupé, il est probablement plus sûr de le faire:this.setState(prevState => prevState);
Mark Galloway
1
Je ne sais pas vraiment pourquoi c'est un «problème». Le nom de la méthode ('forceUpdate') ne peut pas être plus clair: forcez toujours la mise à jour.
Zoman
4

Il existe plusieurs façons de rendre à nouveau votre composant:

La solution la plus simple consiste à utiliser la méthode forceUpdate ():

this.forceUpdate()

Une autre solution consiste à créer la clé non utilisée dans l'état (nonUsedKey) et à appeler la fonction setState avec la mise à jour de cette nonUsedKey:

this.setState({ nonUsedKey: Date.now() } );

Ou réécrivez tout l'état actuel:

this.setState(this.state);

Le changement des accessoires fournit également un rendu des composants.

Jackkobec
la source
3

Pour être complet, vous pouvez également y parvenir dans les composants fonctionnels:

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

Ou, comme crochet réutilisable:

const useForceUpdate = () => {
  const [, updateState] = useState();
  return useCallback(() => updateState({}), []);
}
// const forceUpdate = useForceUpdate();

Voir: https://stackoverflow.com/a/53215514/2692307

Veuillez noter que l'utilisation d'un mécanisme de mise à jour forcée est toujours une mauvaise pratique car elle va à l'encontre de la mentalité de réaction, elle doit donc être évitée si possible.

Lukas Bach
la source
2

Nous pouvons utiliser this.forceUpdate () comme ci-dessous.

       class MyComponent extends React.Component {



      handleButtonClick = ()=>{
          this.forceUpdate();
     }


 render() {

   return (
     <div>
      {Math.random()}
        <button  onClick={this.handleButtonClick}>
        Click me
        </button>
     </div>
    )
  }
}

 ReactDOM.render(<MyComponent /> , mountNode);

La partie Element 'Math.random' du DOM n'est mise à jour que si vous utilisez setState pour restituer le composant.

Toutes les réponses ici sont correctes et complètent la question de la compréhension .. comme nous savons que le rendu d'un composant sans utiliser setState ({}) est en utilisant la forceUpdate ().

Le code ci-dessus s'exécute avec setState comme ci-dessous.

 class MyComponent extends React.Component {



             handleButtonClick = ()=>{
                this.setState({ });
              }


        render() {
         return (
  <div>
    {Math.random()}
    <button  onClick={this.handleButtonClick}>
      Click me
    </button>
  </div>
)
  }
 }

ReactDOM.render(<MyComponent /> , mountNode);
Dhana
la source
1

Juste une autre réponse pour sauvegarder la réponse acceptée :-)

React décourage l'utilisation de, forceUpdate()car ils ont généralement une approche très «c'est la seule façon de le faire» envers la programmation fonctionnelle. C'est bien dans de nombreux cas, mais de nombreux développeurs React ont un arrière-plan OO, et avec cette approche, il est tout à fait correct d'écouter un objet observable.

Et si vous le faites, vous savez probablement que vous DEVEZ restituer lorsque les "déclenchements" observables, et comme tel, vous DEVRIEZ utiliser forceUpdate()et c'est en fait un plus qui shouldComponentUpdate()n'est PAS impliqué ici.

Des outils comme MobX, qui adopte une approche OO, le font en fait sous la surface (en fait, MobX appelle render()directement)

Plaul
la source
0

J'ai préféré éviter forceUpdate (). Une façon de forcer le re-rendu est d'ajouter la dépendance de render () sur une variable externe temporaire et de changer la valeur de cette variable en fonction des besoins.

Voici un exemple de code:

class Example extends Component{
   constructor(props){
      this.state = {temp:0};

      this.forceChange = this.forceChange.bind(this);
   }

   forceChange(){
      this.setState(prevState => ({
          temp: prevState.temp++
      })); 
   }

   render(){
      return(
         <div>{this.state.temp &&
             <div>
                  ... add code here ...
             </div>}
         </div>
      )
   }
}

Appelez this.forceChange () lorsque vous devez forcer un nouveau rendu.

raksheetbhat
la source
0

ES6 - J'inclus un exemple, qui m'a été utile:

Dans une "instruction if courte", vous pouvez passer une fonction vide comme celle-ci:

isReady ? ()=>{} : onClick

Cela semble être l'approche la plus courte.

()=>{}
MrDoda
la source
0

forceUpdate(); fonctionnera mais il est conseillé d'utiliser setState();

Chiamaka Ikeanyi
la source
0

Afin d'accomplir ce que vous décrivez, essayez this.forceUpdate ().

API-Andy
la source
Que fait cette action?
Dominique
0

Une autre façon appelle setState, et préserver l' état:

this.setState(prevState=>({...prevState}));

dvvtms
la source
0

forceUpdate (), mais chaque fois que j'entends quelqu'un en parler, cela a été suivi, vous ne devriez jamais l'utiliser.

Ian
la source
-1

Vous pouvez utiliser forceUpdate () pour plus de détails, vérifiez ( forceUpdate () ).

Harshit Singhai
la source
1
Par curiosité, pourquoi laisser cette réponse si depuis 4 ans les gens proposent déjà ça comme une solution possible dans ce fil?
Byron Coetsee