I've always used redux-thunk or some sort of middleware that grants me access to dispatch/getState, but I have a question that keeps bugging me. Why using that when we can import our store and call store.dispatch()/store.getState().
Later edit:
As a quick example, here's the original code:
function loadData(userId) {
return dispatch => fetch(`http://data.com/${userId}`) // Redux Thunk handles these
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
);
}
Basically, what I'm asking is why is it bad to rewrite this as:
import store from 'our-store';
function loadData(userId) {
return fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => store.dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
err => store.dispatch({ type: 'LOAD_DATA_FAILURE', err })
);
}
Related
There is such kind of code that I have:
const mapStateToProps = (state, ownProps) => ({
historyData: getHistoryForSavedVariants(state)[ownProps.savedVariant.variantId],
isHistoryLoading: getHistoryLoading(state),
})
const mapDispatchToProps = (dispatch, ownProps) => ({
loadData: () => {
-----> dispatch(loadHistoryForSavedVariant(ownProps.savedVariant))
},
})
export default connect(mapStateToProps, mapDispatchToProps)(HistoryButton)
In another file loadHistoryForSavedVariant is the following:
export const loadHistoryForSavedVariant = (savedVariant) => {
return (dispatch) => {
dispatch({ type: REQUEST_HISTORY })
const url = `/api/saved_variant/${savedVariant.variantId}/saved_variant_history`
new HttpRequestHelper(url,
(responseJson) => {
dispatch({ type: RECEIVE_HISTORY })
dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
},
(e) => {
dispatch({ type: RECEIVE_HISTORY })
dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: {} })
},
).get({ xpos: savedVariant.xpos, ref: savedVariant.ref, alt: savedVariant.alt, familyGuid: savedVariant.familyGuids[0] })
}
}
So, as can be seen dispatch ultimately gets a function - (dispatch) => {...} and not an action. Why? I don't understand how that works. On Redux official webpage I see everwhere that dispatch gets an action and not a function, so I am confused. The code is, of course, working fine, I am just interested in this particular mechanism, in whats happening here.
That is a "thunk function". Thunks are a Redux middleware that allow you to pass functions into dispatch(), which is useful for writing async logic separate from your components.
For more details, see these Redux tutorials:
https://redux.js.org/tutorials/fundamentals/part-6-async-logic
https://redux.js.org/tutorials/essentials/part-5-async-logic
My application is calling the action correctly as redux shows it gets the expected data it needs. The problem is it never stores the payload from the action. it is similar to my profile reducer and that works great.
This is the action
export const getCurrentFriends = () => dispatch => {
axios
.get("/api/friends")
.then(res =>
dispatch({
type: GET_FRIENDS,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: {}
})
);
};
This is the reducer
export default function(state = [], action) {
switch (action.type) {
case GET_FRIENDS:
return [
...state,
{
friends: action.payload
}
];
default:
return state;
}
}
For example comparing the two codes below, the first one using async/await and the other calling axios with .then.
What code is recommended?
const BASE_URL = "https://jsonplaceholder.typicode.com"
// async await syntax
export const fetchPosts = () => async dispatch => {
const response = await axios.get(BASE_URL + "/posts")
dispatch({ type: "FETCH_POSTS", payload: response })
}
// using .then instead
export const fetchPosts2 = () => dispatch => {
axios.get(BASE_URL + "/posts").then(response =>
dispatch({
type: "FETCH_POSTS",
payload: response
})
)
}
They're both essentially identical. The only thing it comes down to is pure preference. I personally prefer the async/await syntax because it can save you some potential headaches when doing multiple calls, avoiding some particually nasty nested calls:
// async await syntax
export const fetchPosts = () => async dispatch => {
const posts = await axios.get(BASE_URL + "/posts")
const users = await axios.get(BASE_URL + "/users", {
params: posts.map(p => p.author_id)
})
dispatch({ type: "FETCH_POSTS", payload: {
posts, users
}})
}
vs:
// async await syntax
export const fetchPosts = () => dispatch => {
axios.get(BASE_URL + "/posts").then(posts =>
axios.get(BASE_URL + "/users", {
params: posts.map(p => p.author_id)
}).then(users => {
dispatch({ type: "FETCH_POSTS", payload: {
posts, users
}})
})
)
}
Don't forget about the try/catch syntax as well. You can try/catch entire blocks of code, and then dispatch an error as well. In the later case (Not using async/await), you would need to chain the .then()'s into 2 separate error handlers.
I realized that
const pingEpic = action$ =>
action$.ofType('PING')
.delay(1000) // Asynchronously wait 1000ms then continue
.mapTo({ type: 'PONG' });
mean
dispatch({ type: 'PING' });
dispatch({ type: 'PONG' });
Howeber, I don't realize how to dispatch two actions with pipe.
my code is below
const signUpEpic = (action$: Observable<Action>) => action$.pipe(
ofType(actions.GET_DEVICE_TOKEN),
ofType(actions.SIGN_UP),
exhaustMap(({ payload }) => request({
url: 'users',
method: 'post',
data: {
user: {
email: payload.email,
password: payload.password,
device_token: payload.device_token,
sign_up_with: 'email_and_password',
},
},
}).pipe(
map(data => camelcaseKeysDeep(data)),
map(({ user, authToken }) => currentUserActions.successLogin({ user, authToken })),
catchError((errorMessage: string) => Observable.of(actions.failLogin({ errorMessage }))),
)),
);
Do you have any idea?
I tried below code too.
ofType(actions.GET_DEVICE_TOKEN),
mapTo(actions.SIGN_UP),
thanks
You can use mergeMap to dispatch multiple actions
.pipe(
mergeMap(data => [
camelcaseKeysDeep(data)),
currentUserActions.successLogin(data.user, data.authToken))
],
catchError((errorMessage: string) => Observable.of(actions.failLogin({ errorMessage }))),
)),
How can I continue to pass the payload to the rest of my operators?
For example:
login = (action$: ActionsObservable) => {
return action$.ofType(SessionActions.LOGIN_USER)
.mergeMap(({payload}) => {
return this.http.post(`${BASE_URL}/auth/login`, payload)
.map(result => ({
type: SessionActions.LOGIN_USER_SUCCESS,
payload: result.json().meta
}))
.catch(error => Observable.of({
type: SessionActions.LOGIN_USER_ERROR
}));
});
}
How can I pass the payload and the result to the map operator?
The value for payload is available inside the .map because the function is defined with payload in scope. You can use payload within that function with the way it is currently written:
.map(result => ({
type: SessionActions.LOGIN_USER_SUCCESS,
payload: {
one: result.json().meta,
two: payload /* this is in parent scope from mergeMap */
}
}))
However, if you want to make it more explicit, one way is to use zip to combine observables:
login = (action$: ActionsObservable) => {
return action$.ofType(SessionActions.LOGIN_USER)
.mergeMap(({payload}) => {
return Rx.Observable.zip(
Rx.Observable.of(payload),
this.http.post(`${BASE_URL}/auth/login`, payload),
(payload, result) => { payload, result }
)
.map({ payload, result} => ({
type: SessionActions.LOGIN_USER_SUCCESS,
payload: result.json().meta
}))
.catch(error => Observable.of({
type: SessionActions.LOGIN_USER_ERROR
}));
});
}
Zip lets you combine several observables and provide a selector function to form the resulting object. Depending on your intentions, combineLatest may suit your needs for similar problems, when you want the latest value of each source whenever any source emits.