Purpose of Redux Thunk `([arg(s)]) => dispatch =>`? - reactjs

The code below comes from a Udemy course on the MERN stack by Brad Traversy. I'm new to Redux and Redux Thunk and am trying to understand what the purpose of => dispatch => is. I know it comes from Redux Thunk, which was set up in the Redux store file. I think dispatch is being used here in order to dispatch more than one action from this function and read that the = ([arg(s)]) => dispatch => syntax is an example of currying (though that doesn't seem right since with currying each function has one argument).
I'd greatly appreciate any help understanding => dispatch =>.
(Other minor point of confusion: In the course the function setAlert is referred to as an action, but I'm not sure that's correct since it contains multiple dispatches of actions.)
export const setAlert = (msg, alertType, timeout = 5000) => dispatch => {
const id = uuidv4();
dispatch({
type: SET_ALERT,
payload: { msg, alertType, id }
});
setTimeout(() => dispatch({ type: REMOVE_ALERT, payload: id }), timeout);
};

There's a couple of things going on here:
1) setAlert is what is usually called an "action creator". It's a function that returns an action that you can dispatch later.
2) Redux-Thunk is allowing you to use functions of the form (dispatch) => {} as actions (in place of the more normal object form { type, payload })
It might help if you look at them individually before seeing how they combine together:
// This is an action creator (a function that returns an action)
// This doesn't use redux thunk, the action is just a simple object.
// Because it's an action creator you can pass in arguments
// Because it's not a thunk you can't control when then dispatch happens
const setAlertActionCreator = (msg, alertType) => {
const id = uuidv4();
return {
type: SET_ALERT,
payload: { msg, alertType, id }
};
};
// You use this as:
dispatch(setAlertActionCreator("msg", "type"));
// This is not an action creator it's just an action.
// This IS a redux-thunk action (it's a (dispatch) => {} function not a simple object)
// Because it's not an action creator you can't pass in arguments to get a custom action back
// Because it IS a redux-thunk action you can dispatch more actions later
const setAlertThunk = (dispatch) => {
setTimeout(() => dispatch({
type: SET_ALERT,
payload: {
message: "fixed message",
alertType: "fixed alertType",
id: "fixed id",
}
}), 5000);
};
// You use this as:
dispatch(setAlertThunk);
// When you combine both patterns you gain the ability
// to both pass in arguments and to create additional dispatches
// as the function runs (in this case dispatching again after a timeout.)
// I won't repeat your code, but will show how you would call it:
dispatch(setAlertActionCreator("msg", "type"));
// ie: you use it the same way as example 1.

The syntax () => dispatch => is equivalent to:
function response(){
return function (dispatch){
//Some code here
}
}
So, basically what you can say is, it is a modern way of writing the same function. => dispatch => is returning a function that will be executed when the action will be invoked.
Hope this will help.

=> dispatch =>
is nothing but a syntactical sugar over a function returning another function. Since Fat-arrow functions => are used in place of normal functions.
Like
function abc(a,b){
return a+b;
}
is same as const abc = (a,b) => a+b ;
So you dont have to write return keyword.
So in your case its the same => dispatch => and the one below , its returning an anonymous function with . dispatch as its arguement
function response(){
return (dispatch){
//some code here
}
}
Hope it helps, feel free for doubts

Related

Redux Action Creator and Dispatch confusion with syntax

I'm having some trouble understanding this productListCreator syntax. Using thunk, the tutorial I'm following says that thunk allows us to write the an async function within a function.
productListCreator is a function that returns async function with dispatch as an argument.
But when you use call/use this with dispatch(productListCreator()), I'm confused.
productListCreator has no dispatch argument being passed. Instead it is being passed into the useDispatch hook.
So to me the code is saying useDispatch() which takes productListCreator function and runs it, which has no argument passed in for "dispatch".
import axios from 'axios'
const productListCreator = () => async(dispatch) => {
try {
dispatch({
type: 'PRODUCT_LIST_REQUEST'
})
const { data } = await axios.get('/api/products')
dispatch({
type: 'PRODUCT_LIST_SUCCESS',
payload: data
})
} catch (error) {
dispatch({
type: 'PRODUCT_LIST_ERROR',
payload: error.message
})
}
}
export default productListCreator
const dispatch = useDispatch()
// Retrieve all products at reload
useEffect(()=>{
dispatch(productListCreator())
},[])
productListCreator is a "thunk action creator". It defines the "thunk function", and returns it. That "thunk function" is what gets passed to dispatch. The thunk middleware then intercepts the thunk function, calls it, and passes in (dispatch, getState) as arguments.
I'd recommend reading through the new "Writing Logic with Thunks" docs page, which explains how thunks work.

Redux action dispatch

In my redux action, I have one action will be called by another two actions, code is below:
export const addParticipantFromPopupRequest = (participant, project_id, currentStep) => async (dispatch) => {
const result = await addParticipant(participant)
dispatch({ type: PARTICIPANT_ADD, payload: result })
dispatch(updateProjectStep(project_id, currentStep))
}
export const handleFinalStep = (projectId, currentStep) => async (dispatch) => {
dispatch(updateProjectStep(projectId, currentStep))
}
const updateProjectStep = (projectId, currentStep) => async (dispatch, getState) => {
dispatch({ type: MODAL_STATUS_CHANGE, payload: { projectId, currentStep } })
dispatch({ type: PROJECT_PROCESS_LIST_UPDATE, payload: { project_id: projectId, currentStep } })
const { projectsProcessListsReducer } = getState()
localStorage.setItem("projectsProcessLists", JSON.stringify(projectsProcessListsReducer))
}
If I dont' use dispatch when call updateProjectStep, the addParticipantFromPopupRequest and handleFinalStep cannot run correct.
My question is can I call dispatch actions in this way and is it correct? Why I need the "dispatch" when I call updateProjectStep in another actions rather than call function name directly?
My question is can I call dispatch actions in this way and is it correct?
Yes. You should always call with the dispatch.
Why I need the "dispatch" when I call updateProjectStep in another actions rather than call function name directly?
If you call updateProjectStep directly without dispatch, it will become a normal js function call and your store won't be aware of it. Dispatch is the only way to trigger a state change in store.
In redux the store is single source of truth, the dispatch you are using is actually comes from store (store.dispatch).
If you call a function normally then it won't be aware by the store. That action won't pass through the middlewares (thunk/saga) that store is aware of and won't do the store update via reducers.
If store is not updated, your components won't receive any updates. Eventually your UI won't re-render.
You can find more about dispatch here.

How does a react action have access to dispatch?

In my react project I have an actions file that looks like this:
const startLoad = () => async (dispatch) => {
dispatch({
type: CONTENT_LOAD,
});
};
// Get all content
export const getContents = () => async (dispatch) => {
dispatch(startLoad());
// some more code...
};
So in this example, I know that dispatch is coming from the middleware and getContents has access to it because it was mapped using mapDispatchToProps. So I am assuming when getContents is called, its really called like this -> dispatch(getContents) but can this be replicated in plain JavaScript so I can see what is really going on? If I am wrong about why getContents has access to dispatch please let me know.
For example, how isstartLoad able to use dispatch just because dispatch called startLoad? I have noticed that it will also work if I call it like this: dispatch(startLoad(dispatch));.
Passing dispatch in to startLoad actually makes more sense to me so I don't get why that isn't required.
Edit: this is the closest example I could come up with on my own.
const fun = () => (fun2) => {
fun2();
}
const fun2 = () => {
console.log('hello');
}
fun()(fun2);
So I am assuming when getContents is called, its really called like
this -> dispatch(getContents) but can this be replicated in plain
JavaScript so I can see what is really going on? If I am wrong about
why getContents has access to dispatch please let me know.
redux-thunk has quite simple implementation
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
As you can see it checks if action is a function.
And bonus, if you like you can get the whole state and extra argument in params following dispatch
For example, how isstartLoad able to use dispatch just because
dispatch called startLoad? I have noticed that it will also work if I
call it like this: dispatch(startLoad(dispatch));.
It looks like middleware also handles above case.

How to pass argument to React Redux middleware inside mapDispatchToProps

The situation is I am creating a single board which will hold a collection of note cards (each note has an id, title and body), and each note card will have a button to delete it. Also the application will be syncing with firebase, so my main question is how to pass arguments to middlewares AND do it inside of mapDispatchToProps. The following is my code to point out where my success with middleware and where I am currently blocked.
To hydrate the app on startup, I dispatch a middleware function that gets the data from firebase, and then dispatches actions handled by reducers and finally gets updated by the container/presentation component.
Middleware function:
export function hydrateApp(dispatch) {
dispatch({type: 'PENDING'});
fireBaseDBRef.once('value').then(snapshot => {
let firebaseNotes = snapshot.val()
let notes = [];
// populate notes using firebaseNotes, nothing exciting
dispatch({ type: 'DONE', notes: notes });
// the 'DONE' action.type is handled by the reducer and passes data
// to the container component successfully
}).catch(e => {
dispatch({type: 'ERROR', error: e});
});
}
Container component:
const mapStateToProps = state => {
return {
notes: state.boardReducer.notes
};
};
const mapDispatchToProps = dispatch => {
return {
addNote: () => {
dispatch(boardMiddleware.createNote);
}
};
};
const BoardContainer = connect(
mapStateToProps,
mapDispatchToProps
)(BoardPresentation);
So far so good, and this is what I added to the same middleware and container component files to handle delete scenarios.
Middleware function:
export function deleteNote(id) {
return (dispatch) => {
dispatch({type: 'PENDING'});
//firebase stuff happening here
dispatch((type: 'DONE'});
}
}
Container component:
const mapDispatchToProps = dispatch => {
return {
addNote: () => {
dispatch(boardMiddleware.createNote);
},
removeNote: (id) => {
dispatch(boardMiddleware.deleteNote(id));
}
};
};
The problem is that deleteNote gets called non-stop on startup, I don't even need to click the button.
I know the code presented may not make a whole bunch of sense, but the crux of my problem is that I need to some how pass an id to the middleware function when the user clicks on the button, and because I'm passing the function as a prop, it for some reasons decides to just call it a million times.
I could call boardMiddleware.deleteNote function inside the presentation component just like the examples in the official redux page do, but I'm wondering if there is a way of doing it the way I'm trying to do.
I also thought about binding the argument into the middleware function, but that also doesn't feel right, something like this
removeNote: (id) => {
dispatch(boardMiddleware.deleteNote.bind(id));
}
Thanks for any help in advance!

If an action needs to use some current state, who should fetch it from store?

The title may not clear enough, please consider this example:
If I have a data table, which you can select multiple rows, and click action button like delete.
now in my actions.js:
(selectedRows is an array that contains the row indexes, getSelectedPostIds is a selector which will fetch and convert selectedRows to postIds)
import { getSelectedPostIds } from 'selectors'
export const deletePosts = () => (dispatch, getState) => {
// encapsulate the parameter `postIds` in action
const postIds = getSelectedPostIds(getState())
dispatch({ type: 'DELETE' })
deletePostsApi(postIds)
// .then(...)
// .catch(...)
}
is there any problem in this design? Or I should avoid using getState in an action and just pass postIds as a parameter to the action:
export const deletePosts = postIds => dispatch => {
dispatch({ type: 'DELETE' })
deletePostsApi(postIds)
// .then(...)
// .catch(...)
}
The only difference is that who should fetch the state (use the selector) from store, 1. action or 2. the component who will dispatch the action (via mapStateToProps).
I'm not sure about the approach 1, and the approach 2 will make my component contains a lot of props just because some actions need them (or maybe this is totally fine?).
thanks.
This might be a matter of taste. I usually like to access getState directly since, as you point out, avoids passing a lot of props. And by doing that the action is easier to integrate in different components (I just need to call it instead of additionally editing the mapStateToProps). Also, since in the end both ways are accessing the global store, the intended redux data flow is not compromised in any way.
You can use redux-thunk if you want to work with state in your action creators. :)
https://github.com/gaearon/redux-thunk
function yourActionCreator() {
// Redux-thunk will catch all action creators that return functions
return (dispatch, getState) => {
// u can use state here
const { counter } = getState();
if (counter % 2 === 0) {
return;
}
// Dispatch your action creator as you would normally do
dispatch(increment());
};
}

Resources