I am new to redux. I would like know how I could create my own enhancer in redux. I didn't find any example to create enhancer. To create enhancers, So what arguments do I need to pass and what do I need to return? Is there any rule on creating custom enhancer?
In redux documentation about enhancer, found below two links (no sample or example code)
store-enhancer
using store enhancer
Redux documentation said that,
Middleware adds extra functionality to the Redux dispatch function; enhancers add extra functionality to the Redux store. ... A middleware which logs dispatched actions and the resulting new state. An enhancer which logs the time taken for the reducers to process each action.
So, I am not sure that custom middleware and custom enhancer coding rule are the same like below
const loggerMiddleware = storeAPI => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', storeAPI.getState())
return result
}
So, my question is how to create custom enhancer?
Here is the store enhancer interface
export type StoreEnhancer<Ext = {}, StateExt = never> = (
next: StoreEnhancerStoreCreator<Ext, StateExt>
) => StoreEnhancerStoreCreator<Ext, StateExt>
export type StoreEnhancerStoreCreator<Ext = {}, StateExt = never> = <
S = any,
A extends Action = AnyAction
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
enhancers are high-order functions that take createStore and return a new enhanced version of createStore. Take a look at this sample implementation.
const ourAwesomeEnhancer = createStore => (reducer, initialState, enhancer) => {
const store = createStore(monitoredReducer, initialState, enhancer);
// add enhancer logic
return {
...store
// you can override the some store properties or add new ones
};
};
There is an example in official doc:
const round = number => Math.round(number * 100) / 100
const monitorReducerEnhancer = createStore => (
reducer,
initialState,
enhancer
) => {
const monitoredReducer = (state, action) => {
const start = performance.now()
const newState = reducer(state, action)
const end = performance.now()
const diff = round(end - start)
console.log('reducer process time:', diff)
return newState
}
return createStore(monitoredReducer, initialState, enhancer)
}
export default monitorReducerEnhancer
Related
I have a redux store with 3 reducers:
let reducers = combineReducers({
config: configReducer,
data: dataReducer,
currentState: gameStateRecuder})
let store = createStore(reducers, applyMiddleware(thunkMiddleware));
In each of those reducers the initial store is empty, but once the App component mounts I use useEffect to replace each initial store inside a reducer with the one I receive with axios.get using redux-thunk. It looks like this in every reducer:
let initialState = [];
const SET_STATE = 'SET_STATE';
const configReducer = (state = initialState, action) => {
switch (action.type) {
case SET_STATE: {
return { ...action.state};
}
default:
return state;
}
const setState = (state) => ({ type: SET_STATE, state });
export const getConfigState = () => (dispatch) => {
getAPI.getConfig() //I import getAPI with all the REST API logic//
.then(response => {
dispatch(setState(response));
})
};
And the App trigger is:
const App = (props) => {
useEffect(() => {
props.getConfigState();
props.getDataState();
props.getGameState();
}, []);
return (
//JSX//
);
}
export default compose(connect(null, { getConfigState, getDataState, getGameState }))(App);
However, when the App mounts, I have this mess:
In the end, I get the state of each reducer replaced with the state of the one whose promise resolved the last one. I can try to wrap the app 2 more times with a HOC that does nothing but re-writes a state of the precise reducer, but I would still like to understand what causes a promise to affect other reducers besides the one he needs to effect.
A silly mistake, but maybe someone has the exact same problem - the solution is to give different case names for each reducer - SET_STATE need to become SET_GAME_STATE, SET_CONFIG_STATE, SET_DATA_STATE respectivly. I believe that's because of my misunderstanding on how the dispatch works.
So we may have this in a reducer:
const defaultState = {...};
export const userReducer = (state = defaultState, action: any) => {
// ...
}
is there some way to get a defaultState object for each call to userReducer?
Something like this:
const getDefaultState = () => ({...});
export const userReducer = (state = getDefaultState(), action: any) => {
// ...
}
is this possible in JS? It might not be useful for a Redux reducer, but in general am curious.
Yes, as #blex, pointed out, your intentions are completely doable.
Your snippet has a minor typo that may be causing you issues: parameters with default values (i.e. state) must be ordered after parameters with non-default values (i.e. action).
Here's a minimalist example:
let x = () => 3;
let y = (a, b = x()) => a + b;
console.log(y(5)); // 8
console.log(y(5, 1)); // 6
Store enhancers seem like extension methods in C#, since they add functionality to a Redux data store. Although in most projects, middlewares are enough to modify the behavior of the data store, but the question is:
when is it best to define a store enhancer?
how the input to a store enhancer is provided?
Let's consider the following sample enhancer which tends to dispatch actions asynchronously:
export const asyncEnhancer = delay => createStoreFunction => (...args) => {
const store = createStoreFunction(...args);
return {
...store,
dispatchAsync: (action) => new Promise((resolve, reject) => {
setTimeout(() => {
store.dispatch(action);
resolve();
}, delay);
})
};
}
In the above code, what value is passed to createStoreFunction and args ?
As for applying this enhancer, imagine we have:
export default createStore(myCombinedReducer,
compose(applyMiddleware(m1, m2, thunk), asyncEnhancer(2000)))
The question above, included a simple store enhancer just to dispatch actions asynchronously.
As a Redux API, applyMiddleware is also considered as store enhancer and is the only store enhancer that's included in the Redux library which enables you to wrap the store's dispatch method. According to the documentation, the store enhancer signature is :
createStore => createStore
As a result, having a look at the implementation code of applyMiddleware from docs, will give a great idea on how the input to a store enhancer is provided:
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
The code above, clearly shows the signature createStore => createStore.
applyMiddlaware API returns a function and as soon as you input a createStore into it, it expects you to provide the arguments similar to what is usually provided for createStore API.
So, that's why we can use this shorter notation:
const myStore=applyMiddleware(thunk,logger)(createStore)(rootReducer)
than this longer one:
const middlewares = [
thunk,
logger
]
const myStore = createStore(
rootReducer,
initialState
compose(applyMiddleware(...middlewares))
)
Although in most projects, writing middleware is satisfactory enough, but it's useful to have a high-level understanding of what a store enhancer is.
I have created a Network service component which deals with the API call. I want to retrieve state from other components which update the store.
Im having trouble getting the state so I started using Redux, but I havent used Redux before and still trying to find a way to pass the state to the NetworkService. Any help would be great, thanks!
Here is my NetworkService.js
import RequestService from './RequestService';
import store from '../store';
const BASE_URL = 'api.example.com/';
const REGION_ID = //Trying to find a way to get the state here
// My attempt to get the state, but this unsubscribes and
// doesnt return the value as it is async
let Updated = store.subscribe(() => {
let REGION_ID = store.getState().regionId;
})
class NetworkService {
getForecast48Regional(){
let url =`${BASE_URL}/${REGION_ID }`;
return RequestService.getRequest(url)
}
}
export default new NetworkService();
store.js
import {createStore} from 'redux';
const initialState = {
regionId: 0
};
const reducer = (state = initialState, action) => {
if(action.type === "REGIONAL_ID") {
return {
regionId: action.regionId
};
}
return state;
}
const store = createStore(reducer);
export default store;
My folder heirarchy looks like this:
-App
----Components
----NetworkService
----Store
Do not import store directly. Use thunks/sagas/whatever for these reasons.
NetworkService should not know about anything below.
Thunks know only about NetworkService and plain redux actions.
Components know only about thunks and store (not store itself, but Redux's selectors, mapStateToProps, mapDispatchToProps).
Store knows about plain redux actions only.
Knows - e.g. import's.
//////////// NetworkService.js
const networkCall = (...args) => fetch(...) // say, returns promise
//////////// thunks/core/whatever.js
import { networkCall } from 'NetworkService'
const thunk = (...args) => (dispatch, getState) => {
dispatch(startFetch(...args))
const componentData = args
// I'd suggest using selectors here to pick only required data from store's state
// instead of passing WHOLE state to network layer, since it's a leaking abstraction
const storeData = getState()
networkCall(componentData, storeData)
.then(resp => dispatch(fetchOk(resp)))
.catch(err => dispatch(fetchFail(err)))
}
//////////// Component.js
import { thunk } from 'thunks/core/whatever'
const mapDispatchToProps = {
doSomeFetch: thunk,
}
const Component = ({ doSomeFetch }) =>
<button onClick={doSomeFetch}>Do some fetch</button>
// store.subscribe via `connect` from `react-redux`
const ConnectedComponent = connect(..., mapDispatchToProps)(Component)
Is there a way to structure const reducer = (state = initialState, action) in such a manner that the method isn't bloated by a bunch of switch cases?
My idea was to put related actions in arrays and check them with Array.prototype.includes() when handling an action.
I would then extract the switch cases that correlate to specific actions in new methods (for example the List component would have LIST_ADD, LIST_REMOVE etc.) and call those methods instead of just running through 100 cases in the const reducer = (state = initialState, action)method.
That would tax performance but it would be at least structured.
Any better ideas?
The offical Redux docs provide this very handy reducer creator:
function createReducer(initialState, handlers) {
return function reducer(state = initialState, action) {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state, action)
} else {
return state
}
}
}
which lets you create your reducer as follows:
const reducer = createReducer(initialState, {
[actionType.ACTION1]: specificActionReducer1,
[actionType.ACTION2]: specificActionReducer2,
}
No switch statements!
I use a library called reduxsauce which removes the need for large switch statements.
https://github.com/infinitered/reduxsauce
Instead it binds actions to methods with this syntax:
export const INITIAL_STATE = {
values: {},
}
export const reducerFunction = (state, action) => {
const values = action.value;
return {
...state,
values,
};
};
// map the action types to the reducer functions
export const HANDLERS = {
[Type.ACTION_NAME]: reducerFunction,
...
}
// call createReducer to magically tie it all together
export default createReducer(INITIAL_STATE, HANDLERS);
You could try redux-named-reducers for this as well. Allows you to compose reducers like so:
moduleA.reduce(SOME_ACTION, action => ({ state1: action.payload }))
moduleA.reduce(SOME_OTHER_ACTION, { state2: "constant" })
It has the added benefit of being able to access the reducer state anywhere, like in mapDispatchToProps for example:
const mapDispatchToProps = dispatch => {
return {
onClick: () => {
dispatch(someAction(getState(moduleA.state1)));
}
};
};