Reputation: 4077
@connect
works great when I'm trying to access the store within a react component. But how should I access it in some other bit of code. For eg: let's say I want to use an authorization token for creating my axios instance that can be used globally in my app, what would be the best way to achieve that?
This is my 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
Now I want to access a data point from my store, here is what that would look like if I was trying to fetch it within a react component using @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() {...}
}
Any insights or workflow patterns out there?
Upvotes: 331
Views: 249019
Reputation: 361
Accessing the Redux store outside a React component is generally discouraged because Redux is primarily designed to manage application state within React components. However, there are scenarios where you might need to access the Redux store outside of React components, such as in middleware, utility functions, or other parts of your application logic.
but still some approaches you can consider for accessing the Redux store outside of a React component:
1.Passing the Store as a Parameter
import store from './redux/store';
function myUtilityFunction(store) {
const state = store.getState();
// utility opertations
}
myUtilityFunction(store);
2.Using Redux Toolkit's createSlice:
import { createSlice } from '@reduxjs/toolkit';
import store from './redux/store';
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: {
increment(state) {
return state + 1;
},
decrement(state) {
return state - 1;
},
},
});
export const { increment, decrement } = counterSlice.actions;
// Access the state and dispatch actions
console.log(store.getState());
store.dispatch(increment());
Upvotes: 1
Reputation: 99
Importing store directly is not the correct way!!!
Here are the possible ways using which you can access redux store outside a react component
My favorite is the third option of injecting the store when the app is being created:
One common use case is reading API authorization information such as a token from the Redux state, inside of an Axios interceptor. The interceptor file needs to reference store.getState(), but also needs to be imported into API layer files, and this leads to circular imports.
You can expose an injectStore function from the interceptor file instead:
common/api.js
let store
export const injectStore = _store => {
store = _store
}
axiosInstance.interceptors.request.use(config => {
config.headers.authorization = store.getState().auth.token
return config
})
Then, in your entry point file, inject the store into the API setup file:
index.js
import store from './app/store'
import { injectStore } from './common/api'
injectStore(store)
This way, the application setup is the only code that has to import the store, and the file dependency graph avoids circular dependencies.
Above ways are suggested by react redux itself. You can check here!
Upvotes: 2
Reputation: 13
I faced a similar problem - I wanted to set up a global Axios configuration that could be accessed from anywhere within React (component, outside of component, inside of thunk action..)
I ended up writing a thunk that returns the config object. The benefit is that thunks have access to the store thanks to getState()
so you don't have to reinvent the wheel. Maybe this solution helps someone ;)
1. THUNK
export const getAxiosConfig = (payload) => {
return (dispatch, getState) => {
const { app } = getState();
const axiosConfig: AxiosRequestConfig = {
baseURL: `${process.env.BACKEND_API}`,
headers: {
Authorization: `Bearer ${app.token}`
}
};
return axiosConfig;
}
}
2. GET THE CONFIG
const axiosConfig = dispatch(getAxiosConfig(null));
3. MAKE API CALL WITH CONFIG
const { data } = await axios.get(
/resource/${resourceId}
, axiosConfig );
Upvotes: 1
Reputation: 2518
You can use Middleware
according to How can I access the store in non react components?:
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);
}
}
import { createStore, applyMiddleware } from 'redux'
const serviceMiddleware = myServiceMiddleware(myService)
const store = createStore(reducer, applyMiddleware(serviceMiddleware))
Further Reading: Redux Docs > Middleware
Upvotes: 18
Reputation: 59
export my store variable
export const store = createStore(rootReducer, applyMiddleware(ReduxThunk));
in action file or your file need them import this (store)
import {store} from "./path...";
this step get sate from store variable with function
const state = store.getState();
and get all of state your app
Upvotes: 5
Reputation: 698
It might be a bit late but i think the best way is to use axios.interceptors
as below. Import urls might change based on your project setup.
import axios from 'axios';
import setupAxios from './redux/setupAxios';
import store from './redux/store';
// some other codes
setupAxios(axios, store);
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)
);
}
Upvotes: 11
Reputation: 624
Doing it with hooks. I ran into a similar problem, but I was using react-redux with hooks. I did not want to lard up my interface code (i.e., react components) with lots of code dedicated to retrieving/sending information from/to the store. Rather, I wanted functions with generic names to retrieve and update the data. My path was to put the app's
const store = createSore(
allReducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
into a module named store.js
and adding export
before const
and adding the usual react-redux imports in the store.js. file. Then, I imported to index.js
at the app level, which I then imported into index.js with the usual import {store} from "./store.js"
The child components then accessed the store using the useSelector()
and useDispatch()
hooks.
To access the store in non-component front end code, I used the analogous import (i.e., import {store} from "../../store.js"
) and then used store.getState()
and store.dispatch({*action goes here*})
to handled retrieving and updating (er, sending actions to) the store.
Upvotes: 8
Reputation: 29071
Export the store from the module you called createStore
with. Then you are assured it will both be created and will not pollute the global window space.
const store = createStore(myReducer);
export store;
or
const store = createStore(myReducer);
export default store;
import {store} from './MyStore'
store.dispatch(...)
or if you used default
import store from './MyStore'
store.dispatch(...)
If you need multiple instances of a store, export a factory function.
I would recommend making it async
(returning a promise
).
async function getUserStore (userId) {
// check if user store exists and return or create it.
}
export getUserStore
On the client (in an async
block)
import {getUserStore} from './store'
const joeStore = await getUserStore('joe')
Upvotes: 218
Reputation: 52
An easy way to have access to the token, is to put the token in the LocalStorage or the AsyncStorage with React Native.
Below an example with a React Native project
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;
}
};
...
and 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 };
For web you can use Window.localStorage
instead of AsyncStorage
Upvotes: 0
Reputation: 519
Like @sanchit proposed middleware is a nice solution if you are already defining your axios instance globally.
You can create a middleware like:
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;
And use it like this:
import { createStore, applyMiddleware } from 'redux';
const store = createStore(reducer, applyMiddleware(axiosAuth))
It will set the token on every action but you could only listen for actions that change the token for example.
Upvotes: 13
Reputation: 701
You can use store
object that is returned from createStore
function (which should be already used in your code in app initialization). Than you can use this object to get current state with store.getState()
method or store.subscribe(listener)
to subscribe to store updates.
You can even save this object to window
property to access it from any part of application if you really want it (window.store = store
)
More info can be found in the Redux documentation .
Upvotes: 39
Reputation: 69928
For TypeScript 2.0 it would look like this:
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(...)}
Upvotes: 5
Reputation: 4077
Found a solution. So I import the store in my api util and subscribe to it there. And in that listener function I set the axios' global defaults with my newly fetched token.
This is what my new api.js
looks like:
// 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
Maybe it can be further improved, cause currently it seems a bit inelegant. What I could do later is add a middleware to my store and set the token then and there.
Upvotes: 77