web-dev-qa-db-fra.com

Comment accéder à un objet historique en dehors d'un composant React

Tout d'abord, je connais assez bien le withRouter HoC, cependant, dans ce cas, cela n'aide pas car je ne veux pas accéder à l'objet history dans un composant.

J'essaie de réaliser un mécanisme qui redirigera l'utilisateur vers la page de connexion si je reçois en retour un 401 d'un point de terminaison API. Pour faire des requêtes http, j'utilise axios . J'ai environ 60 points de terminaison que je dois couvrir, qui sont utilisés dans une douzaine de composants dans toute mon application.

Je veux créer une fonction décoratrice pour l'objet instance axios, qui:

1. makes the request
2. if fail && error_code = 401, update user route to `/login`
3. if success, return promise

Le problème que j'ai avec ce qui précède est de mettre à jour l'itinéraire de l'utilisateur. Auparavant, dans react-router-v3, J'aurais pu importer l'objet browserHistory directement à partir du package react-router, ce qui n'est plus possible.

Donc, ma question est, comment puis-je accéder à l'objet historique en dehors du composant React sans le faire passer par la pile des appels?

25
Dragos Rizescu

react-router v4 fournit également un moyen de partager l'historique via le package history, à savoir la fonction createBrowserHistory().

L'important est de s'assurer que le même objet historique est partagé dans votre application. Pour ce faire, vous pouvez profiter du fait que les modules de nœuds sont des singletons.

Créez un fichier appelé history.js dans votre projet, avec le contenu suivant:

import { createBrowserHistory } from 'history';

const history = createBrowserHistory();
export default history;

Vous pouvez ensuite simplement l'importer dans votre application via:

import history from "./history.js";

Veuillez noter que seul Router accepte le prop history (BrowserRouter ne le fait pas), alors assurez-vous de mettre à jour votre routeur JSX en conséquence:

import { Router } from "react-router-dom";
import history from "./history.js";

// and then in your JSX:
return (
  <Router history={history}>
    {/* routes as usuall */}
  </Router>
)

Un exemple de travail peut être trouvé sur https://codesandbox.io/s/owQ8Wrk

28
BTMPL

Je viens de rencontrer ce même problème et voici la solution que j'ai utilisée pour résoudre ce problème.

J'ai fini par créer une fonction d'usine qui renvoie un objet qui a toutes mes fonctions de services. Pour appeler cette fonction d'usine, un objet de la forme suivante doit être fourni.

interface History {
    Push: (location: string) => void;
}

Voici une version distillée de ma fonction d'usine.

const services = {};

function servicesFactory(history: History) {
    const countries = countriesFactory(history);
    const local = {
        ...countries,
    };
    Object.keys(local).forEach(key => {
        services[key] = local[key];
    });

}

Maintenant, le fichier où cette fonction est définie exporte 2 choses.

1) Cette fonction d'usine

2) l'objet services.

Voici à quoi ressemble le service des pays.


function countriesFactory(h: History): CountriesService {
    const countries: CountriesService = {
        getCountries() {
            return request<Countries>({
                method: "get",
                endpoint: "/api/countries",
            }, h)
        }
    }
    return countries;
}

Et enfin voici à quoi ressemble ma fonction request.

function request<T>({ method, endpoint, body }: Request, history: History): Promise<Response<T>> {
    const headers = {
        "token": localStorage.getItem("someToken"),
    };
    const result: Response<T> = {
        data: null,
        error: null,
    };
    return axios({
        url: endpoint,
        method,
        data: body,
        headers,
    }).then(res => {
        result.data = res.data;
        return result;
    }).catch(e => {
        if (e.response.status === 401) {
            localStorage.clear();
            history.Push("/login");
            return result;
        } else {
            result.error = e.response.data;
            return result;
        }
    });
}

Comme vous pouvez le voir, la fonction request demande à ce que l'objet historique lui soit transmis, ce qu'il obtiendra du service, et le service l'obtiendra de la fabrique de services.

Maintenant, la partie intéressante est que je n'ai plus qu'à appeler cette fonction d'usine et à lui passer l'objet historique une fois dans l'application entière. Après cela, je peux simplement importer l'objet services et utiliser n'importe quelle méthode dessus sans avoir à me soucier de lui passer l'objet historique.

Voici le code de l'endroit où j'appelle la fonction d'usine de services.

const App = (props: RouteComponentProps) => {
  servicesFactory(props.history);
  return (
    // my app and routes
  );
}

J'espère que quelqu'un d'autre qui trouvera cette question trouvera cela utile.

1
Chaim Friedman