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.
Related
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,
}
}
}
This code is the actual implementation of applymiddleware form https://github.com/reduxjs/redux/blob/4.x/src/applyMiddleware.js . In this code mapping a middlewares passing middlewareAPI as argument to each middelware
let's take example thunk function
In that thunk function I am getting a dispatch and getState as arguments to handle async actions
please observe my question i am try to understand my doubt clearly
In that applyMiddleware function each middleware is passing a middlewareAPI as argument but inside middlewareAPI object dispatch is a some anonymous function returning a other function named as disptach. In the place of store we are passing a middlewareAPI object But how thunk function is getting a composed disptach as argument
My Doubts:
1.dispatch: (...args) => dispatch(...args) what is the meaning to this line and how it's going help in that code
2.we are passing middlewareAPI in the place of store object before store.dispatch function is updating and my thunk function is getting arguments as diapatch and getState. From redux-thunk my thunk function will be calling with diapatch and getState arguments. How dispatch function is getting updated dispatch function as argument to my thunk function
I'm new to react, redux and tyring to understand the redux-toolkit tutorial i follow. I have the slice as follows.
const initialState = {
count: 0,
};
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.count += 1;
},
},
});
export const { increment } = counterSlice.actions;
export default counterSlice.reducer;
export const incrementTest = () => (dispatch) => {
dispatch(increment());
};
Then I use that incrementTest action as follows
<button onClick={() => dispatch(incrementTest())}> + </button>
I want to understand following.
In following fuction
export const incrementTest = () => (dispatch) => {
dispatch(increment());
};
we return a function which takes argument as dispatch then call that provided dispatch function with argement of another function increment which is defined above and exported.
However when we call this function we use dispatch(incrementTest()) providing incrementTest as a param to dispatch. I don't understand this concept . Which concept in javascript should i further study to learn this ?
Also increment reducer take state as parameter ( and action also in some cases ). Who provide this (state,action) to this function as we call it as dispatch(incrementTest())
So this:
export const incrementTest = () => (dispatch) => {
dispatch(increment());
};
is an example for a thunk, a function that gets called (by a redux middleware) with two arguments, dispatch and getState. It is typically used to coordinate async work since you can await stuff inside the function or deal with promises. Look up the thunk middleware if you want to know more. I'm not sure why they made this action a thunk, there's no need for it, maybe testing.
To your second question, the library does. All you do is call dispatch() with an action, the library calls the reducer function with the current state and your action. Think of it as emitting an event. Your job is to create the event, the library takes care of updating the global state accordingly. The reducer is written declaratively, sort of. As in "how would the global state need to change if that specific event happened?".
I was following along with the Redux Essentials Tutorials on how to employ createAsyncThunk for generating Thunks. In their example here they create a thunk like so:
export const addNewPost = createAsyncThunk(
'posts/addNewPost',
async (initialPost) => { // Note: initialPost is an object with 3 props: title, content, user
const response = await client.post('/fakeApi/posts', initialPost)
return response.data
}
)
and they call it in another file like this:
await dispatch(addNewPost({ title, content, user: userId }))
In my project I've installed typescript and react types (#types/react). Even though the code is JavaScript, this gives me intellisense from VSCode IDE on proper typings. However when I do above I see:
The typings expects 0 arguments but gets one, my object. Hovering over the method addNewPost I see that it takes no args and returns the async action.
How can I have my IDE and typescript typings support recognize the the proper params required?
What I tried
Tried to add some JSDOC string to the created addNewPost function like so:
/**
* addNewPost
* #returns {(initialPost:{title:string, content:string, user: string}) => void} the returned action create takes an object as arg
*/
export const addNewPost = createAsyncThunk(
'posts/addNewPost',
async (initialPost) => {
const response = await client.post('/fakeApi/posts', initialPost)
...
Following another Stack Overflow suggestion on how to use JSDocs to describe a returned function. but that doesn't seem to work.
Anyone have any suggestions?
The issue is that the default Dispatch type from Redux only understands that the dispatch() function can accept plain action objects. It does not know that a thunk function is a valid thing that can be passed in.
In order to make this code work correctly, you need to follow our instructions for setting up the store and inferring the real type of dispatch based on all the actual middleware that were configured, which would usually include the thunk middleware:
https://redux.js.org/tutorials/typescript-quick-start
Then, use that AppDispatch type elsewhere in the app, so that when you do try to dispatch something TS recognizes that thunks are a valid thing to pass in.
So, typically:
// store.ts
const store = configureStore({
reducer: {
posts: postsReducer,
comments: commentsReducer,
users: usersReducer
}
})
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
// hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
// MyComponent.ts
import { useAppDispatch } from '../../app/hooks'
export function MyComponent() {
const dispatch = useAppDispatch()
const handleClick = () => {
// Works now, because TS knows that `dispatch` is `AppDispatch`,
// and that the type includes thunk handling
dispatch(someThunk())
}
}
Additionally, in your specific case, you're using createAsyncThunk. You need to tell TS what the types are for its arguments, per https://redux-toolkit.js.org/usage/usage-with-typescript#createasyncthunk:
export const addNewPost = createAsyncThunk(
'posts/addNewPost',
async (initialPost: InitialPost) => {
// The actual `client` in the Essentials tutorial is plain JS
// But, if we were using Axios, we could do:
const response = await client.post<ResultPost>('/fakeApi/posts', initialPost)
// If the `client` was written well, `.data` is of type `ResultPost`
return response.data
}
)
I had some luck with revising the JSDoc to be specifically above the payloadCreator param inside the createAsyncThunk function like so:
export const addNewPost = createAsyncThunk(
'posts/addNewPost',
/**
* Make POST request to API w. params and create a new record
* #param {{content: string, title: string, user: string}} initialPost
* #returns {Promise<{content: string, date: string, id: string, reactions: Object, title: string, user: string}>} returned data
*/
async (initialPost) => {
const response = await client.post('/fakeApi/posts', initialPost)
// The response includes the complete post object, including unique ID
return response.data
}
)
As I look more closely at how createAsyncThunk works I realize why this makes more sense as createAsyncThunk itself is not returning these values, they are params and return types of the functions we are passing to it.
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
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)