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

192

@connectfunciona muy bien cuando intento acceder a la tienda dentro de un componente de reacción. Pero, ¿cómo debo acceder a él en algún otro bit de código? Por ejemplo: digamos que quiero usar un token de autorización para crear mi instancia de axios que pueda usarse 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 desde mi tienda, así es como se vería si estuviera tratando de recuperarlo 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() {...}
}

¿Alguna idea o patrones de flujo de trabajo por ahí?

Subodh Pareek
fuente
¿No quieres que tu instancia Axios viva en un middleware redux? Estará disponible para todas sus aplicaciones de esta manera
Emrys Myrooin
Puede importar el apien Appclase y después de obtener el token de autorización que puede hacer api.defaults.headers.common['Authorization'] = this.props.auth.tokens.authorization_token;, y al mismo tiempo también puede almacenarlo en localStorage, por lo que cuando el usuario actualice la página, puede verificar si el token existe en localStorage y si sí, puedes configurarlo. Creo que será mejor configurar el token en el módulo api tan pronto como lo obtengas.
Dhruv Kumar Jha
1
Dan Abromov proporciona un ejemplo en la cola de problemas aquí: github.com/reactjs/redux/issues/916
chrisjlee
1
Si solo necesita acceder a un estado particular desde un reductor particular, puede probar con redux-named-redurs que le permite acceder al último estado desde cualquier lugar.
miles_christian

Respuestas:

146

Exporte la tienda desde el módulo al que llamó 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 por defecto

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')
Steven Spungin
fuente
47
ADVERTENCIA para aplicaciones isomórficas: ¡hacer esto del lado del servidor compartirá la tienda redux entre todos sus usuarios!
Lawrence Wagerfield
66
La pregunta tampoco indica explícitamente "navegador". Dado que tanto Redux como JavaScript se pueden usar en el servidor o en el navegador, es mucho más seguro ser específico.
Lawrence Wagerfield
77
exportar tienda parece crear una pesadilla de importaciones cíclicas, createStore incluye sus reductores, que requieren sus acciones (al menos el tipo de acción enumeración e interfaces de acción), que no deben importar nada que intente importar la tienda. Por lo tanto, no debe usar store en sus reductores o acciones (o discutir de otra manera la importación cíclica).
Eloff el
66
Esta es la respuesta correcta, pero si (como yo) quiere leer en lugar de expedición de una acción en la tienda, es necesario llamar astore.getState()
Juan José Ramírez
3
Bueno, mi interpretación sobre "acceso redux store" fue "leer" la tienda. Solo trato de ayudar a otros.
Juan José Ramírez
47

Encontró una solución. Así que importo la tienda en mi API y me suscribo allí. Y en esa función de escucha configuré los valores predeterminados globales de los 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

Tal vez 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.

Subodh Pareek
fuente
1
¿Puedes compartir cómo se store.jsve tu archivo?
Vic
exactamente algo que estaba buscando, muchas gracias @subodh
Harkirat Saluja
1
Sé que la pregunta es antigua, pero puede aceptar su propia respuesta como correcta. Esto hace que su respuesta sea más fácil de encontrar para otros que eventualmente pueden venir aquí.
Filipe Merker
3
Obtuve "TypeError: WEBPACK_IMPORTED_MODULE_2__store_js .b no está definido" cuando intento acceder a la tienda fuera de un componente o una función. ¿Alguna ayuda sobre por qué es eso?
ben
21

Puede usar el storeobjeto que se devuelve de 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 para almacenar actualizaciones.

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 .

generador de basura
fuente
19
suena un poco hacky para salvar storealwindow
Vic
3
@Vic Claro que sí :) 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ó su vida "createStore" y luego simplemente importarlo.
trashgenerator
Tengo varios iframes que necesitan acceder al estado de los otros iframes. Sé que es un poco raro, pero creo que tener la tienda en la ventana sería mejor que usar mensajes para iframes. ¿¿Alguna idea?? @Vic @trashgenerator?
Sean Malter
10

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

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 úsalo así:

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

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

erksch
fuente
¿Cómo puede lograr lo mismo con una instancia de axios personalizada?
Anatol
3

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(...)}
Ogglas
fuente
3
Si está votando en contra, agregue un comentario por qué.
Ogglas
3
Votado abajo porque su respuesta carece de explicación y usa TypeScript en lugar de Javascript.
Será
2
@ Will Gracias por decir por qué. Imao, el código no requiere especificación, pero si desea que se explique algo específico, diga qué. De hecho, se usa TypeScript, pero si se eliminan los tipos, el mismo código se ejecutará en ES6 sin ningún problema.
Ogglas
Tenga en cuenta que esto será muy malo si está haciendo renderizado del lado del servidor, compartirá el estado con todas las solicitudes.
Rob Evans
2

Puede ser un poco tarde, pero creo que la mejor manera es usar axios.interceptors 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)
    );
}
S. Mert
fuente
1

Haciéndolo con ganchos. Me encontré con un problema similar, pero estaba usando react-redux con ganchos. No quería acumular mi código de interfaz (es decir, componentes de reacción) con un montón de 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 era poner la aplicación de

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, importé a index.jsnivel de 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 eluseSelector()useDispatch() ganchos y .

Para acceder a la tienda en código front-end que no es componente, utilicé la importación análoga (es decir, import {store} from "../../store.js") y luego usé store.getState()y store.dispatch({*action goes here*})manejé la recuperación y actualización (er, enviando acciones a) la tienda.

Charles Goodwin
fuente
0

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

Debajo de 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

Denis Zheng
fuente