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?".
Related
I am a bit conflicted about this and seem to find no answer.
I have a slice with some actions. What I want to do is to fire a request without awaiting it's result (it's a firebase call that should be handled optimistically).
How should I then structure my code around this case? Using createAsyncThunk shouldn't be considered because I am not awaiting anything and dont need to update any local requestStatus variables.
This is what I have right now:
// Component.js (where I do the dispatch)
useEffect(() => dispatch(updateCheckIn(payload)), [])
// CheckInActions.js
import { updateCheckInInDb } from "../../api/checkin"
import { updateCheckInAction } from "../reducers"
export const updateCheckIn = (update) => {
updateCheckInInDb(update) // make API call
return updateCheckInAction(update) // return actual reducer action
}
// CheckInReducer.js
const checkInSlice = createSlice({
name: "checkIn",
initialState: INITIAL_STATE,
reducers: {
updateCheckInAction(state, action) {
return updateStateViaUpdateApi(state.data, action)
},
},
})
export const { updateCheckInAction } = checkInSlice.actions
export default checkInSlice
This works but I feel it is a bit awkward (specially the naming). I would need to call one a updateCheckInAction and the other updateCheckIn. I am a bit new to redux toolkit. Isn't there a more elegant way of doing this?
The most idiomatic answer here would be hand-write a thunk that kicks off the async request and then dispatches the plain action:
export const updateCheckIn = (update) => {
return (dispatch) => {
// make API call
updateCheckInInDb(update)
// dispatch actual action to update state
dispatch(updateCheckInAction(update))
}
}
// used as
dispatch(updateCheckIn(123))
See https://redux.js.org/usage/writing-logic-thunks for details on what thunks are overall and how to write them.
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 know solutions like redux thunk exist when you want to dispatch action asynchronously. However, lately I had following situation:
import {store} from "./store";
const initialState = {
todos: []
}
function todoApp(state = initialState, action) {
if(action.type == "ACTION_A"){
// 1. do smth with state
// 2. do smth with state, and then... schedule a dispatch say using setTimeout:
setTimeout(()=>store.dispatch({type:"ACTION_B", payload:1}), 2000);
return state;
}// check other actions e.g. ACTION_B etc.
return state;
}
You can see ACTION_B isn't an action I would like to dispatch from somewhere else say as an async action (so that I could use redux thunk say), rather it is part of the logic in ACTION_A.
My question is: how are such situations handled in redux?
PS. This answer, says it is fine to schedule a dispatch in reducer (my situation above) and even gives some solution using middleware. However, I followed that solution to a blog post (see comments on that answer) and saw comments by Mark Erikson (maintainer of Redux) on blog, that that is still not the right way to do it. He seems to suggest redux-loop for such situation.
My question is what are the right ways to handle such situations in redux?
Are there other solutions also apart from redux-loop?
Or can we still solve this situation using redux thunk?
This is a good case for a thunk:
const actionA = () = ({ dispatch, getState }) => {
dispatch(actionA1) // dispatch another action that will change the state
setTimeout(()=> {
const { data } = getState();
dispatch({type:"ACTION_B", payload: data });
}, 2000);
}
or a custom middleware, that will schedule the timeout, but will allow actionA to continue to the reducer, and change the state (this will happen before the timeout, because it's synchronous):
const middleware = ({ dispatch, getState }) = next => action => {
if(action.type == "ACTION_A"){ //
setTimeout(()=> {
const { data } = getState();
dispatch({type:"ACTION_B", payload: data });
}, 2000);
}
next(action);
}
In general reducers should be pure functions, ie no side effects like scheduling or dispatching actions. If an action needs to do something other than changing the state, it should use a middleware (thunk for example) to do so.
This might be a question of best practices but I'd appreciate an explanation on why this doesn't work. I'm using Typescript + Redux + Thunk and trying to call actions like this:
export const requestUserDashboards = createAction<DashboardModel>(Type.REQUEST_USER_DASHBOARDS);
Dispatch in the fetch:
export const fetchDashboards = () => {
return async (dispatch: Dispatch, getState: any) => {
try {
dispatch(requestUserDashboards({
currentDashboard: getState.currentDashboard,
dashboards: getState.dashboards,
hasDashboards: false,
error: getState.error
}))
...
}
})
}
Here's the corresponding reducer:
export const dashboardReducer = handleActions<RootState.DashboardState, DashboardModel>(
{
[DashboardActions.Type.REQUEST_USER_DASHBOARDS]: (state = initialState, action): RootState.DashboardState => ({
currentDashboard: action.payload!.currentDashboard,
dashboards: action.payload!.dashboards,
hasDashboards: action.payload!.hasDashboards,
error: action.payload!.error
})
},
initialState
);
dispatch is working, however, getState doesn't correctly collect the current store state. I'm testing this by doing the following in the component receiving the updated store:
componentWillReceiveProps(nextProps: Login.Props) {
console.log(nextProps.defaultAccounts.defaultAccount);
}
Calling this in the component using:
this.props.defaultAccountActions.fetchUserDefaultAccount();
The action is working as the values from the fetch are being captured.
However, where I am using the getState.xxxx, these values are returning as undefined:
index.tsx:84 Uncaught TypeError: Cannot read property 'defaultAccount' of undefined
The initialState from my reducer is working. I can see this from doing the console.log(this.props.defaultAccounts.defaultAccount) from the componentWillMount() function.
I'm not sure what else I can provide. I think I'm actually just fundamentally misunderstanding how actions/reducers manage the store.
Questions
I am trying to get the current store values by using the getState.xxxx in the dispatch. Is this the correct way to do this?
isn't getState a function in that place? So you would need to do something
const state = getState();
and then use state inside dispatch
found in documentation, yeah it is a function at that place so you should firstly invoke a function to get state and then use it (e.g. from documentation below)
function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState();
if (counter % 2 === 0) {
return;
}
dispatch(increment());
};
}
If you are using mapstatetoprops in your component you can use that to get the values from store. mapStateToProps first argument is actually the Redux state. It is practically an abstracted getState().
const mapStateToProps = function(state, ownProps) {
// state is equivalent to store.getState()
// you then get the slice of data you need from the redux store
// and pass it as props to your component
return {
someData: state.someData
}
}
I am using redux and redux-thunk. In a lot of my action creators I use helpers in which I pass dispatch to, I'm wondering if this is bad practice or an anti-pattern or can I avoid passing dispatch around like this.
For instance, I have an action creator like so:
import * from "./helpers";
export const listenForMessages = () => async (dispatch) => {
helpers.updateListening(true, dispatch);
}
And I have a helper inside helpers.js like so:
export const updateListening(isListening = false, dispatch) {
// do something interesting
disptach(...);
}
I'd like to avoid having to pass dispatch around. It feels ugly.
Instead of calling dispatch(someAction) in updateListening, make it return the action object instead:
export const updateListening(isListening = false) {
// do something interesting
return { type: SOME_ACTION, payload };
}
This makes updateListening just like any other action creators, so you can dispatch the action in the caller with:
export const listenForMessages = () => async (dispatch) => {
dispatch(helpers.updateListening(true)); // or `bindActionCreators`
}
If updateListening needs to complete async calls before dispatching, then you can either return a promise and await it in listenForMessages, or turn it into an async action creator just like your listenForMessages, then you can dispatch it in exactly the same way: dispatch(helpers.updateListening(true)).
As #riwu said, passing dispatch down is redundant, you can have helpers which simply return an action object.
Additionally, if you are using react-redux, action creators can be wrapped into dispatch automatically if you pass them as an object to mapDispatchToProps.
https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options