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
Related
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 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.
I'd set redux custom middleware, and change on action to insert argument on action. But the result is actions must be plain objects. use custom middleware for async actions because in action i did't return dispatch(myAction)
my configure middleware
const injectMiddleware = ({dispatch, getState}) => next => action => {
//skipped all my logic
return(
typeof action === 'function' ?
next(action({dispatch, getState, ...anotherCustomFunction}))
:
next(action)
)
}
my Actions
export const setUserSessionToken = () => ({dispatch}: Store) => {
dispatch(setToken)
}
and get Error
actions must be plain objects. use custom middleware for async actions
fixed with return
export const setUserSessionToken = () => ({dispatch}: Store) => {
return dispatch(setToken)
}
and no error without return if not using custom middleware
export const setUserSessionToken = () => (dispatch) => {
dispatch(setToken)
}
or in custom middleware just do
return next(action)
Change this
next(action({dispatch, getState, ...anotherCustomFunction}))
as
action({dispatch, getState, ...anotherCustomFunction})
calling next is passing an action object to the next middleware. Of course, you can pass a function that not evaluated yet, but you will need another middleware like thunk to handle the function eventually.
I'm trying to pass in a variable into an axios request in my action:
export function searchRequest(search){
return(dispatch)=>{
console.log('in search', search)
return axios.get(`http://localhost:4000/reports/${search}`)
.then(response => {
dispatch(searchInfo(response.data))
})
}
}
When I console log search, it does not register.
However, when I remove the return dispatch and console log response.data in the .then, I get the desired data, but I'm not able to use dispatch.
The question is, why am I not able to pass in search in this way?
Edit: this is in react native
I'm not sure how you connect this function to Redux in React-native but I think it works like you're doing it with regular React.
The searchRequest function return a new function with one variable dispatch. search will be "static" with the value you've passed in when calling searchRequest, so probably null or undefined or some hardcoded value.
You should to this the other way around:
import {connect} from 'react-redux'
const searchRequest = (dispatch) => {
return async (search) => {
const response = await axios.get(`http://localhost:4000/reports/${search}`)
dispatch(searchInfo(response.data))
}
}
class YourComponent extends Component {
....
onSearch = (search) => {
this.props.doSearch(search)
}
....
}
const mapDispatch = (dispatch) => ({
doSearch: searchRequest(dispatch)
})
export connect(null, mapDispatch)(YourComponent)
This is how it looks with "normal web"-React. Maybe in native it looks different. Notice that dispatch is provided as parameter once. Afterwards when calling doSearch the same instance of dispatch is used inside the function.
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.