¿Cuál es la mejor manera de acceder a la tienda redux fuera de un componente de reacción?

247

@connectfunciona muy bien cuando intento acceder a la tienda dentro de un componente de reacción. Pero, ¿cómo debería acceder a él en algún otro fragmento de código? Por ejemplo: digamos que quiero usar un token de autorización para crear mi instancia de axios que se pueda usar globalmente en mi aplicación, ¿cuál sería la mejor manera de lograrlo?

Este es mi 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

Ahora quiero acceder a un punto de datos de mi tienda, así es como se vería si estuviera tratando de buscarlo dentro de un componente de reacción usando @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() {...}
}

¿Algún conocimiento o patrón de flujo de trabajo?

4
  • ¿No quiere que su instancia de Axios viva en un middleware redux? Estará disponible por toda su aplicación de esta manera 19/07/2016 a las 14:21
  • Puede importar el apiin Appclass y después de obtener el token de autorización puede hacerlo api.defaults.headers.common['Authorization'] = this.props.auth.tokens.authorization_token;, y al mismo tiempo puede almacenarlo en localStorage también, por lo que cuando el usuario actualiza la página, puede verificar si el token existe en localStorage y si lo hace, puede configurarlo. Creo que será mejor configurar el token en el módulo api tan pronto como lo obtenga. 5 de septiembre de 2016 a las 8:55
  • 1
    Dan Abromov proporciona un ejemplo en la cola de problemas aquí: github.com/reactjs/redux/issues/916 9 de marzo de 2017 a las 5:31
  • 1
    Si solo necesita acceder a un estado en particular desde un reductor en particular, puede probar con redux-named-reducers que le permite acceder al estado más reciente desde cualquier lugar. 7 de abril de 2018 a las 8:16
170

Exporta la tienda desde el módulo con el que llamaste createStore. Entonces tiene la seguridad de que se creará y no contaminará el espacio de la ventana global.

MyStore.js

const store = createStore(myReducer);
export store;

o

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

MyClient.js

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

o si usaste default

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

Para casos de uso de múltiples tiendas

Si necesita varias instancias de una tienda, exporte una función de fábrica. Recomendaría hacerlo async(devolviendo a promise).

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

En el cliente (en un asyncbloque)

import {getUserStore} from './store'

const joeStore = await getUserStore('joe')
17
  • 61
    ADVERTENCIA para aplicaciones isomorfas: ¡¡¡haciendo esto del lado del servidor compartirá la tienda redux entre todos sus usuarios !!! 5 de diciembre de 2017 a las 10:00
  • 6
    La pregunta tampoco dice explícitamente "navegador". Dado que tanto Redux como JavaScript se pueden usar tanto en el servidor como en el navegador, es mucho más seguro ser específico. 6 de diciembre de 2017 a las 10:29
  • 9
    exportar tienda parece crear una pesadilla de importaciones cíclicas, createStore incluye tus reductores, que requieren tus acciones (al menos el tipo de acción enum e interfaces de acción), que no deben importar nada que intente importar tienda. Por lo tanto, no debe usar store ni en sus reductores ni en sus acciones (o disputar de otra manera la importación cíclica).
    Eloff
    29/07/18 a las 19:59
  • 8
    Esta es la respuesta correcta, pero si usted (como yo) desea leer en lugar de enviar una acción en la tienda, debe llamar store.getState() 5 de septiembre de 2018 a las 21:55
  • 4
    Bueno, mi interpretación de "acceder a la tienda redux" fue "leer" la tienda. Solo intento ayudar a los demás. 6 de septiembre de 2018 a las 22:29
59

Encontré una solución. Así que importo la tienda en mi api util y me suscribo allí. Y en esa función de escucha configuré los valores predeterminados globales de axios con mi token recién obtenido.

Así es api.jscomo se ve mi nuevo :

// 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

Quizás se pueda mejorar aún más, porque actualmente parece un poco poco elegante. Lo que podría hacer más tarde es agregar un middleware a mi tienda y configurar el token allí mismo.

6
  • 1
    ¿Puedes compartir cómo se store.jsve tu archivo?
    Vic
    13/10/2016 a las 17:04
  • exactamente algo que estaba buscando, muchas gracias @subodh 3 de mayo de 2017 a las 8:01
  • 1
    Sé que la pregunta es antigua, pero puede aceptar su propia respuesta como la correcta. Esto hace que su respuesta sea más fácil de encontrar para otras personas que eventualmente puedan venir aquí. 14/06/18 a las 13:24
  • 3
    Obtuve "TypeError: WEBPACK_IMPORTED_MODULE_2__store_js .b is undefined" cuando intento acceder a la tienda fuera de un componente o una función. ¿Alguna ayuda sobre por qué es eso?
    ben
    28 de septiembre de 2018 a las 15:43
  • @ Subodh, ¡creo que ya no funciona!
    Por
    23 de abril a las 2:31
26

Puede usar el storeobjeto que se devuelve desde la createStorefunción (que ya debería usarse en su código en la inicialización de la aplicación). Entonces puede usar este objeto para obtener el estado actual con el store.getState()método o store.subscribe(listener)suscribirse a las actualizaciones de la tienda.

Incluso puede guardar este objeto en la windowpropiedad para acceder a él desde cualquier parte de la aplicación si realmente lo desea ( window.store = store)

Se puede encontrar más información en la documentación de Redux .

3
  • 22
    suena un poco hacky para guardar storeen elwindow
    Vic
    13/10/2016 a las 17:04
  • 4
    @Vic Claro que lo es :) Y normalmente no quieres hacerlo. Solo quería mencionar que puedes hacer lo que quieras con esta variable. Probablemente la mejor idea sería almacenarlo en el archivo donde creó sus vidas "createStore" y luego simplemente importarlo. 18/10/2016 a las 13:14
  • Tengo varios iframes que necesitan acceso al estado de los otros iframes. Sé que es un poco hacky, pero creo que tener la tienda en la ventana sería mejor que usar mensajes en iframes. ¿¿Alguna idea?? @Vic @trashgenerator? 24/07/19 a las 21:25
10

Como @sanchit, el middleware propuesto es una buena solución si ya está definiendo su instancia de axios globalmente.

Puede crear un middleware como:

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;

Y utilícelo así:

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

Establecerá el token en cada acción, pero solo puede escuchar las acciones que cambian el token, por ejemplo.

1
  • 1
    ¿Cómo se puede lograr lo mismo con una instancia personalizada de axios?
    Anatol
    13/07/20 a las 18:25
10

Se puede utilizar de Middlewareacuerdo con ¿Cómo puedo acceder a la tienda en componentes que no reaccionan? :

Middleware

function myServiceMiddleware(myService) {
  return ({ dispatch, getState }) => next => action => {
    if (action.type == 'SOMETHING_SPECIAL') {
      myService.doSomething(getState());
      myService.doSomethingElse().then(result => dispatch({ type: 'SOMETHING_ELSE', result }))
    }
    return next(action);
  }
}

Uso

import { createStore, applyMiddleware } from 'redux'
const serviceMiddleware = myServiceMiddleware(myService)
const store = createStore(reducer, applyMiddleware(serviceMiddleware))

Lectura adicional : Redux Docs> Middleware

5

Para TypeScript 2.0 se vería así:

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(...)}
4
  • 6
    Si está votando en contra, agregue un comentario por qué.
    Ogglas
    21 dic 2017 a las 7:39
  • 3
    Votó en contra porque su respuesta carece de explicación y usa TypeScript en lugar de Javascript.
    Will
    28 feb 2018 a las 21:06
  • 2
    @Will Gracias por decir por qué. Imao, el código no requiere especificación, pero si desea que se le explique algo específico, diga qué. De hecho, se usa TypeScript, pero si se eliminan los mecanografiados, el mismo código se ejecutará en ES6 sin problemas.
    Ogglas
    28 feb 2018 a las 21:19
  • Tenga en cuenta que esto será muy malo si está haciendo la representación del lado del servidor, compartirá el estado con todas las solicitudes. 17 de mayo de 2019 a las 18:51
5

Puede que sea un poco tarde, pero creo que la mejor manera es usarlo axios.interceptorscomo se muestra a continuación. Las URL de importación pueden cambiar según la configuración de su proyecto.

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)
    );
}
4

exportar mi variable de tienda

export const store = createStore(rootReducer, applyMiddleware(ReduxThunk));

en el archivo de acción o su archivo los necesita importar esto (almacenar)

import {store} from "./path...";

este paso obtiene sate de la variable de tienda con función

const state = store.getState();

y obtenga todo el estado de su aplicación

3

Haciéndolo con ganchos. Me encontré con un problema similar, pero estaba usando react-redux con ganchos. No quería aumentar mi código de interfaz (es decir, componentes de reacción) con mucho código dedicado a recuperar / enviar información desde / hacia la tienda. Más bien, quería funciones con nombres genéricos para recuperar y actualizar los datos. Mi camino fue poner la aplicación

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

en un módulo llamado store.jsy agregando exportantes consty agregando las importaciones habituales de react-redux en store.js. expediente. Luego, lo importé al index.jsnivel de la aplicación, que luego importé a index.js con los import {store} from "./store.js"componentes secundarios habituales y luego accedí a la tienda usando los hooks useSelector()y useDispatch().

Para acceder a la tienda en un código de front-end sin componentes, utilicé la importación análoga (es decir, import {store} from "../../store.js") y luego usé store.getState()y store.dispatch({*action goes here*})para manejar la recuperación y actualización (es decir, enviar acciones a) la tienda.

1
  • ¡Encuentro el truco con la exportación de la tienda desde el índice, etc., extremadamente útil! 3 de septiembre a las 3:53
0

Una forma fácil de tener acceso al token es poner el token en LocalStorage o AsyncStorage con React Native.

A continuación, un ejemplo con un proyecto 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;
  }
};
...

y 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 };

Para la web puede usar en Window.localStoragelugar de AsyncStorage

1
  • No creo que esto sea útil porque está accediendo al almacenamiento local directamente, sin usar la tienda redux. 3 de agosto a las 13:11