Quelle est la meilleure façon d'accéder au magasin redux en dehors d'un composant de réaction?

192

@connectfonctionne très bien lorsque j'essaie d'accéder au magasin dans un composant de réaction. Mais comment dois-je y accéder dans un autre morceau de code. Par exemple: disons que je souhaite utiliser un jeton d'autorisation pour créer mon instance axios qui peut être utilisée globalement dans mon application, quel serait le meilleur moyen d'y parvenir?

C'est mon api.js

// tooling modules
import axios from 'axios'

// configuration
const api = axios.create()
api.defaults.baseURL = 'http://localhost:5001/api/v1'
api.defaults.headers.common['Authorization'] = 'AUTH_TOKEN' // need the token here
api.defaults.headers.post['Content-Type'] = 'application/json'

export default api

Maintenant, je veux accéder à un point de données de mon magasin, voici à quoi cela ressemblerait si j'essayais de le récupérer dans un composant de réaction en utilisant @connect

// connect to store
@connect((store) => {
  return {
    auth: store.auth
  }
})
export default class App extends Component {
  componentWillMount() {
    // this is how I would get it in my react component
    console.log(this.props.auth.tokens.authorization_token) 
  }
  render() {...}
}

Avez-vous des idées ou des modèles de flux de travail?

Subodh Pareek
la source
Vous ne voulez pas que votre instance Axios vive dans un middleware redux? Il sera disponible par toute votre application de cette façon
Emrys Myrooin
Vous pouvez importer le apidans la Appclasse et après avoir obtenu le jeton d'autorisation, vous pouvez le faire api.defaults.headers.common['Authorization'] = this.props.auth.tokens.authorization_token;, Et en même temps, vous pouvez le stocker dans localStorage également, donc lorsque l'utilisateur actualise la page, vous pouvez vérifier si le jeton existe dans localStorage et s'il fait, vous pouvez le définir., Je pense qu'il sera préférable de définir le jeton sur le module api dès que vous l'obtiendrez.
Dhruv Kumar Jha
1
Dan Abromov fournit un exemple dans la file d'attente des problèmes ici: github.com/reactjs/redux/issues/916
chrisjlee
1
Si vous avez juste besoin d'accéder à un état particulier à partir d'un réducteur particulier, vous pouvez essayer avec redux-named-reducers, cela vous permet d'accéder au dernier état de n'importe où.
miles_christian

Réponses:

146

Exportez le magasin depuis le module createStoreavec lequel vous avez appelé . Ensuite, vous êtes assuré qu'il sera à la fois créé et ne polluera pas l'espace de la fenêtre globale.

MyStore.js

const store = createStore(myReducer);
export store;

ou

const store = createStore(myReducer);
export default store;

MyClient.js

import {store} from './MyStore'
store.dispatch(...)

ou si vous avez utilisé par défaut

import store from './MyStore'
store.dispatch(...)

Pour les cas d'utilisation de plusieurs magasins

Si vous avez besoin de plusieurs instances d'un magasin, exportez une fonction d'usine. Je recommanderais de le faire async(retourner un promise).

async function getUserStore (userId) {
   // check if user store exists and return or create it.
}
export getUserStore

Sur le client (dans un asyncbloc)

import {getUserStore} from './store'

const joeStore = await getUserStore('joe')
Steven Spungin
la source
47
AVERTISSEMENT pour les applications isomorphes: faire ce côté serveur partagera le magasin redux entre tous vos utilisateurs !!!
Lawrence Wagerfield
6
La question n'indique pas non plus explicitement "navigateur". Étant donné que Redux et JavaScript peuvent être utilisés sur un serveur ou un navigateur, il est beaucoup plus sûr d'être spécifique.
Lawrence Wagerfield
7
l'exportation du magasin semble créer un cauchemar d'importations cycliques, createStore inclut vos réducteurs, qui nécessitent vos actions (au moins le type d'action enum et les interfaces d'action), qui ne doivent importer rien qui tente d'importer le magasin. Vous ne devez donc pas utiliser store dans vos réducteurs ou vos actions (ou vous disputer d'une autre manière autour de l'importation cyclique.)
Eloff
6
C'est la bonne réponse, mais si vous (comme moi) voulez lire au lieu d'envoyer une action dans le magasin, vous devez appelerstore.getState()
Juan José Ramírez
3
Eh bien, mon interprétation sur "accéder au magasin redux" était "lire" le magasin. J'essaye juste d'aider les autres.
Juan José Ramírez
47

J'ai trouvé une solution. J'importe donc le magasin dans mon utilitaire api et je m'y abonne. Et dans cette fonction d'écoute, j'ai défini les valeurs par défaut globales de l'axios avec mon jeton récemment récupéré.

Voici à quoi api.jsressemble mon nouveau :

// tooling modules
import axios from 'axios'

// store
import store from '../store'
store.subscribe(listener)

function select(state) {
  return state.auth.tokens.authentication_token
}

function listener() {
  let token = select(store.getState())
  axios.defaults.headers.common['Authorization'] = token;
}

// configuration
const api = axios.create({
  baseURL: 'http://localhost:5001/api/v1',
  headers: {
    'Content-Type': 'application/json',
  }
})

export default api

Peut-être que cela peut être encore amélioré, car actuellement cela semble un peu inélégant. Ce que je pourrais faire plus tard, c'est ajouter un middleware à mon magasin et définir le jeton sur-le-champ.

Subodh Pareek
la source
1
pouvez-vous partager à quoi ressemble votre store.jsfichier?
Vic
exactement quelque chose que je cherchais, merci beaucoup @subodh
Harkirat Saluja
1
Je sais que la question est ancienne, mais vous pouvez accepter votre propre réponse comme étant la bonne. Cela rend votre réponse plus facile à trouver pour les autres qui pourraient éventuellement venir ici.
Filipe Merker
3
J'ai "TypeError: WEBPACK_IMPORTED_MODULE_2__store_js .b is undefined" lorsque j'essaye d'accéder au magasin en dehors d'un composant ou d'une fonction. Une aide sur pourquoi c'est?
ben le
21

Vous pouvez utiliser un storeobjet renvoyé par la createStorefonction (qui devrait déjà être utilisé dans votre code lors de l'initialisation de l'application). Ensuite, vous pouvez utiliser cet objet pour obtenir l'état actuel avec la store.getState()méthode ou store.subscribe(listener)pour vous abonner pour stocker les mises à jour.

Vous pouvez même enregistrer cet objet dans la windowpropriété pour y accéder depuis n'importe quelle partie de l'application si vous le souhaitez vraiment ( window.store = store)

Plus d'informations peuvent être trouvées dans la documentation Redux .

poubelle
la source
19
semble un peu piraté pour enregistrer storesur lewindow
Vic
3
@Vic Bien sûr :) Et normalement, vous ne voulez pas le faire. Je voulais juste mentionner que vous pouvez faire ce que vous voulez avec cette variable. La meilleure idée serait probablement de le stocker dans le fichier où vous avez créé votre "createStore" et de l'importer à partir de celui-ci.
trashgenerator
J'ai plusieurs iframes qui ont besoin d'accéder à l'état des autres iframes. Je sais que c'est un peu piraté mais je pense qu'avoir le magasin sur la fenêtre serait mieux que d'utiliser des messages dans des iframes. Des pensées?? @Vic @trashgenerator?
Sean Malter
10

Comme @sanchit, le middleware proposé est une bonne solution si vous définissez déjà votre instance axios globalement.

Vous pouvez créer un middleware comme:

function createAxiosAuthMiddleware() {
  return ({ getState }) => next => (action) => {
    const { token } = getState().authentication;
    global.axios.defaults.headers.common.Authorization = token ? `Bearer ${token}` : null;

    return next(action);
  };
}

const axiosAuth = createAxiosAuthMiddleware();

export default axiosAuth;

Et utilisez-le comme ceci:

import { createStore, applyMiddleware } from 'redux';
const store = createStore(reducer, applyMiddleware(axiosAuth))

Il définira le jeton à chaque action, mais vous ne pourrez écouter que les actions qui modifient le jeton par exemple.

Erksch
la source
comment pouvez-vous obtenir la même chose avec une instance axios personnalisée?
Anatol le
3

Pour TypeScript 2.0, cela ressemblerait à ceci:

MyStore.ts

export namespace Store {

    export type Login = { isLoggedIn: boolean }

    export type All = {
        login: Login
    }
}

import { reducers } from '../Reducers'
import * as Redux from 'redux'

const reduxStore: Redux.Store<Store.All> = Redux.createStore(reducers)

export default reduxStore;

MyClient.tsx

import reduxStore from "../Store";
{reduxStore.dispatch(...)}
Ogglas
la source
3
Si vous votez contre, veuillez ajouter un commentaire pourquoi.
Ogglas
3
Voté vers le bas parce que votre réponse manque d'explication et utilise TypeScript plutôt que Javascript.
Sera le
2
@Will Merci d'avoir dit pourquoi. Imao le code ne nécessite pas de spécification, mais si vous souhaitez quelque chose de spécifique expliqué, veuillez dire quoi. TypeScript est en effet utilisé mais si les typages sont supprimés, le même code s'exécutera dans ES6 sans problème.
Ogglas
Gardez à l'esprit que ce sera très mauvais si vous effectuez un rendu côté serveur, il partagera l'état avec toutes les requêtes.
Rob Evans
2

Il est peut-être un peu tard mais je pense que le meilleur moyen est d'utiliser axios.interceptorscomme ci-dessous. Les URL d'importation peuvent changer en fonction de la configuration de votre projet.

index.js

import axios from 'axios';
import setupAxios from './redux/setupAxios';
import store from './redux/store';

// some other codes

setupAxios(axios, store);

setupAxios.js

export default function setupAxios(axios, store) {
    axios.interceptors.request.use(
        (config) => {
            const {
                auth: { tokens: { authorization_token } },
            } = store.getState();

            if (authorization_token) {
                config.headers.Authorization = `Bearer ${authorization_token}`;
            }

            return config;
        },
       (err) => Promise.reject(err)
    );
}
S. Mert
la source
1

Le faire avec des crochets. J'ai rencontré un problème similaire, mais j'utilisais react-redux avec des hooks. Je ne voulais pas larder mon code d'interface (c'est-à-dire les composants de réaction) avec beaucoup de code dédié à la récupération / l'envoi d'informations depuis / vers le magasin. Je voulais plutôt des fonctions avec des noms génériques pour récupérer et mettre à jour les données. Mon chemin était de mettre l'appli

const store = createSore(
   allReducers,
   window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
 );

dans un module nommé store.jset en ajoutant exportavant constet en ajoutant les importations react-redux habituelles dans le store.js. fichier. Ensuite, j'ai importé au index.jsniveau de l'application, que j'ai ensuite importé dans index.js avec les import {store} from "./store.js"composants enfants habituels , puis ont accédé au magasin à l'aide des crochets useSelector()et useDispatch().

Pour accéder au magasin en code frontal non-composant, j'ai utilisé l'importation analogue (c'est-à-dire import {store} from "../../store.js"), puis j'ai utilisé store.getState()et store.dispatch({*action goes here*})géré la récupération et la mise à jour (euh, l'envoi d'actions) au magasin.

Charles Goodwin
la source
0

Un moyen simple d'accéder au jeton consiste à placer le jeton dans LocalStorage ou AsyncStorage avec React Native.

Ci-dessous un exemple avec un projet React Native

authReducer.js

import { AsyncStorage } from 'react-native';
...
const auth = (state = initialState, action) => {
  switch (action.type) {
    case SUCCESS_LOGIN:
      AsyncStorage.setItem('token', action.payload.token);
      return {
        ...state,
        ...action.payload,
      };
    case REQUEST_LOGOUT:
      AsyncStorage.removeItem('token');
      return {};
    default:
      return state;
  }
};
...

et api.js

import axios from 'axios';
import { AsyncStorage } from 'react-native';

const defaultHeaders = {
  'Content-Type': 'application/json',
};

const config = {
  ...
};

const request = axios.create(config);

const protectedRequest = options => {
  return AsyncStorage.getItem('token').then(token => {
    if (token) {
      return request({
        headers: {
          ...defaultHeaders,
          Authorization: `Bearer ${token}`,
        },
        ...options,
      });
    }
    return new Error('NO_TOKEN_SET');
  });
};

export { request, protectedRequest };

Pour le Web, vous pouvez utiliser à la Window.localStorageplace d'AsyncStorage

Denis Zheng
la source