Accéder au contexte React en dehors de la fonction de rendu

93

Je développe une nouvelle application en utilisant la nouvelle API React Context au lieu de Redux, et avant, avec Redux, lorsque j'avais besoin d'obtenir une liste d'utilisateurs par exemple, j'appelle simplement componentDidMountmon action, mais maintenant avec React Context, mes actions vivent à l'intérieur mon consommateur qui est à l'intérieur de ma fonction de rendu, ce qui signifie que chaque fois que ma fonction de rendu est appelée, elle appellera mon action pour obtenir ma liste d'utilisateurs et ce n'est pas bon car je vais faire beaucoup de requêtes inutiles.

Alors, comment puis-je appeler une seule fois mon action, comme dans componentDidMountau lieu d'appeler dans render?

Juste pour illustrer, regardez ce code:

Supposons que j'emballe tout mon Providersdans un seul composant, comme ceci:

import React from 'react';

import UserProvider from './UserProvider';
import PostProvider from './PostProvider';

export default class Provider extends React.Component {
  render(){
    return(
      <UserProvider>
        <PostProvider>
          {this.props.children}
        </PostProvider>
      </UserProvider>
    )
  }
}

Ensuite, je mets ce composant Provider enveloppant toute mon application, comme ceci:

import React from 'react';
import Provider from './providers/Provider';
import { Router } from './Router';

export default class App extends React.Component {
  render() {
    const Component = Router();
    return(
      <Provider>
        <Component />
      </Provider>
    )
  }
}

Maintenant, à la vue de mes utilisateurs par exemple, ce sera quelque chose comme ceci:

import React from 'react';
import UserContext from '../contexts/UserContext';

export default class Users extends React.Component {
  render(){
    return(
      <UserContext.Consumer>
        {({getUsers, users}) => {
          getUsers();
          return(
            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
          )
        }}
      </UserContext.Consumer>
    )
  }
}

Ce que je veux, c'est ceci:

import React from 'react';
import UserContext from '../contexts/UserContext';

export default class Users extends React.Component {
  componentDidMount(){
    this.props.getUsers();
  }

  render(){
    return(
      <UserContext.Consumer>
        {({users}) => {
          getUsers();
          return(
            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
          )
        }}
      </UserContext.Consumer>
    )
  }
}

Mais bien sûr, l'exemple ci-dessus ne fonctionne pas car les getUsersaccessoires de vue de mes utilisateurs ne sont pas présents. Quelle est la bonne façon de procéder si cela est possible?

Gustavo Mendonça
la source

Réponses:

144

EDIT: Avec l'introduction de react -hooks dans la v16.8.0 , vous pouvez utiliser le contexte dans les composants fonctionnels en utilisant useContexthook

const Users = () => {
    const contextValue = useContext(UserContext);
    // rest logic here
}

EDIT: à partir de la version 16.6.0 . Vous pouvez utiliser le contexte dans la méthode du cycle de vie en utilisant this.contextcomme

class Users extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* perform a side-effect at mount using the value of UserContext */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* render something based on the value of UserContext */
  }
}
Users.contextType = UserContext; // This part is important to access context values

Avant la version 16.6.0, vous pouviez le faire de la manière suivante

Afin d'utiliser le contexte dans votre méthode de style de vie, vous écririez votre composant comme

class Users extends React.Component {
  componentDidMount(){
    this.props.getUsers();
  }

  render(){
    const { users } = this.props;
    return(

            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
    )
  }
}
export default props => ( <UserContext.Consumer>
        {({users, getUsers}) => {
           return <Users {...props} users={users} getUsers={getUsers} />
        }}
      </UserContext.Consumer>
)

En règle générale, vous maintenez un contexte dans votre application et il est logique de regrouper la connexion ci-dessus dans un HOC afin de la réutiliser. Vous pouvez l'écrire comme

import UserContext from 'path/to/UserContext';

const withUserContext = Component => {
  return props => {
    return (
      <UserContext.Consumer>
        {({users, getUsers}) => {
          return <Component {...props} users={users} getUsers={getUsers} />;
        }}
      </UserContext.Consumer>
    );
  };
};

et ensuite vous pouvez l'utiliser comme

export default withUserContext(User);
Shubham Khatri
la source
C'est une façon de le faire! Mais j'emballe mes composants avec d'autres trucs, donc le code deviendra de plus en plus compliqué de cette façon. Ainsi, l'utilisation de la bibliothèque with-context et de ses décorateurs rend le code beaucoup plus simple. Mais merci pour la réponse!
Gustavo Mendonça
Cela ne fonctionne pas pour moi, j'utilise le github with-context
PassionateDeveloper
1
Shubham Khatri a raison dans sa réponse, il y a juste une petite faute de frappe qui empêche cela de fonctionner. Les accolades empêchent le retour implicite. Remplacez-les simplement par des parenthèses.
VDubs du
1
Pouvez-vous expliquer comment utiliser le this.contextavec plusieurs contextes dans le même composant? Supposons que j'ai un composant qui a besoin d'accéder à UserContext et PostContext, comment gérer cela? Ce que je peux voir, c'est créer un contexte mélangeant les deux, mais c'est la seule ou la meilleure solution pour cela?
Gustavo Mendonça
1
@ GustavoMendonça Vous ne pouvez vous abonner qu'à un seul contexte en utilisant l'implémentation de l'API this.context. Si vous avez besoin d'en lire plus d'un, vous devez suivre le modèle d'accessoires de rendu
Shubham Khatri
3

Ok, j'ai trouvé un moyen de le faire avec une limitation. Avec la with-contextbibliothèque, j'ai réussi à insérer toutes mes données de consommation dans mes accessoires de composants.

Mais, pour insérer plus d'un consommateur dans le même composant est compliqué à faire, il faut créer des consommateurs mixtes avec cette bibliothèque, ce qui rend le code pas élégant et non productif.

Le lien vers cette bibliothèque: https://github.com/SunHuawei/with-context

EDIT: En fait, vous n'avez pas besoin d'utiliser l'API multi-contexte qui with-contextfournit, en fait, vous pouvez utiliser l'API simple et créer un décorateur pour chacun de vos contextes et si vous souhaitez utiliser plus d'un consommateur dans votre composant, il suffit déclarez au-dessus de votre classe autant de décorateurs que vous le souhaitez!

Gustavo Mendonça
la source
1

Pour ma part, il suffisait d'ajouter .bind(this)à l'événement. Voici à quoi ressemble mon composant.

// Stores File
class RootStore {
   //...States, etc
}
const myRootContext = React.createContext(new RootStore())
export default myRootContext;


// In Component
class MyComp extends Component {
  static contextType = myRootContext;

  doSomething() {
   console.log()
  }

  render() {
    return <button onClick={this.doSomething.bind(this)}></button>
  }
}
Ballpin
la source
1
ce code semble beaucoup plus simple, mais il n'accède pas du tout aux valeurs ou n'interagit pas du tout avec l'instance RootStore. pouvez-vous montrer un exemple divisé en deux fichiers et des mises à jour réactives de l'interface utilisateur?
dcsan
-8

Vous devez passer le contexte dans le composant parent supérieur pour obtenir l'accès en tant qu'accessoire dans child.

kprocks
la source