I'm using redux-thunk and I also want to dispatch some actions with timeout. Because of some reasons (i want all timeouts in an object, i want to able to cancel them, doesnt really matter now) I want to have custom 'timeout middleware' and 'action enchancer'
enchancer just emits special type of action:
const addTimeoutToAction = (delay, action) => ({
type: 'TIMEOUT'
, delay
, action
})
middleware just catches it and should dispatch action after timeout ends
({dispatch, getState}) => next => action => {
if (action && action.type === 'TIMEOUT') {
setTimeout(() => {
dispatch(action.action);
}, action.delay)
}
next(action);
}
So my expectation is that dispatch function in the middleware will send action back to the middleware chain, where it will start to go through all again.
My example code works with plain action, however thunked action is not. please help me understand how to reroute delayed action back to middleware chain.
Example code:
http://codepen.io/Fen1kz/pen/zKadmL?editors=0010
You code should look like this
const action3 = () => (dispatch, getState) => {
dispatch({
type: 'action3'
});
}
Whenever you use thunk middleware, you MUST call dispatch to dispatch actions, you cannot return an object.
Here is the corrected codepen: http://codepen.io/anon/pen/pEKWRK?editors=0010
Hope this helps.
Related
I'm using Redux Toolkit, though I don't think that makes a difference.
I've set up a snackbar that reads from store.data.message, and I write the message by setting a value and then clearing the message after a timeout. This happens in a helper function, showMessage.
I call showMessage from my thunks:
export const showMessage = (dispatch: any, message: string) => {
dispatch(setMessage(message))
setTimeout(() => dispatch(clearMessage()), 3000)
}
export const fetchDataState = (): AppThunk => async dispatch => {
const state = await getSystemState()
showMessage(dispatch, 'Fetched system state.')
dispatch(getStateSucceeded(state))
}
I simply want to know if there is a way to write these without having to pass dispatch in every time I call showMessage.
Correct me if I'm wrong, but I imagine I can't write it like a thunk because redux-thunk is middleware that calls the thunks in its own way, and I'm not calling them that way.
Yes, you can write it as a thunk like this:
export const showMessage = (message: string) => (dispatch: AppDispatch) => {
dispatch(setMessage(message))
setTimeout(() => dispatch(clearMessage()), 3000)
}
// call it:
dispatch(showMessage("Hi!"))
Hello and thanks in advance :)
Main Idea
I want to launch specific action$ epics from redux-observable depends on route and cancel them when route changes.
Also I want to handle some clean up when epics are canceled. I have done it. But:
Problem
I'm using state.dispatch(actions.signInError({})) to clean up (that is deprecated), and don't know how to do it another way. My code is below, problem at the very end.
Change epics when route is changed
/**
* Launch route specific actions if such exist and cancel previous one
*
* #param {Function} action$ - redux-observable action$
* #param {Object} state - redux state
*/
const addEpicsForRoute = (action$, state) => action$
.ofType(LOCATION_CHANGE) // action from route
.switchMap(
( action ) => {
// get epics for route
const epicsForRoute = routeEpics[ action.payload.pathname ];
if ( epicsForRoute ) {
return merge(
...epicsForRoute.map(observableCreator => observableCreator(action$, state))
);
} else {
return empty(); // no specific epics
}
}
);
Some specific epic for some route
/**
* Handle xhr request-response/error logic of sign in user
*
* #param {Function} action$
* #param {Object} state
*/
export const signIn = ( action$, state ) => {
return action$
.ofType(types.SIGN_IN_REQUEST)
.mergeMap(( { params, } ) => (
Observable.create(observer => {
services
.signInRequest( // it is ajax observable
mappers.mapSignInRequest(params)
)
.map(response => actions.signInJWTSuccess( // dispatch success
mappers.mapUser(response)
))
.catch(error => of(actions.signInError( // dispatch error
mappers.mapSignInError(error)
)))
.subscribe(( value ) => { // pass action to redux-store
observer.next(value);
});
return () => {
// cleanup logic. HERE IS A PROBLEM
// calling store.dispatch() directly in your Epics is deprecated and will be removed.
// what should I use instead?
state.dispatch(actions.signInError({}));
};
})
));
};
Also I am new for rxjs and if you have an advice how I can improve or make look code prettier I'm more than interested!
I suggest to review the flow of the code to leverage more the power of Observables operators.
An idea could be to move along these lines
export const signIn = ( action$, state ) => {
return action$
.ofType(types.SIGN_IN_REQUEST)
.switchMap(( { params, } ) => (services.signInRequest( // it is ajax observable
mappers.mapSignInRequest(params)
))
.map(response => actions.signInJWTSuccess( // dispatch success
mappers.mapUser(response)
))
.catch(error => of(actions.signInError( // dispatch error
mappers.mapSignInError(error)
)))
};
In this way you have created a function, signIn, that returns an Observable which emits the result of the signIn ajax call.
Then, I would create another piece of logic to subscribe to such Observable returned by signIn and decide what to do, e.g.
const subscription = signIn(action, state)
.subscribe(
value => {// do what needs to be done with the result of the signIn call},
err => {// insert here the logic to handle error conditions},
() => {// do here what needs to be done when the Observable completes
// consider that ajax calls complete after the first emit, therefore
// you can put this part of logic also within the first callback, the one passed as the first parameter to subscribe() method
}
)
Note that you are also storing the subscription in a variable, that you can use to unsubscribe when the route changes.
The cleanup logic you are putting in the function returned by the create method should be probably moved to the place where you actually unsubscribe the subscription because you move to another route.
I think a good question is whether this is correct expectation. When you use switchMap you unsubscribe from the inner Observable which means you no longer want to receive its emissions. So does it make sense that it'll emit yet another action when you unsubscribe?
Anyway you could merge another Observable to the chain that emits only the cleanup actions.
const action$ = new Subject();
const cleanup$ = new Subject();
action$
.pipe(
switchMap(() => new Observable(observer => {
// whatever goes here
observer.next('Observable created');
return () => {
cleanup$.next(/* create proper action here */ 'cleanup');
};
})),
merge(cleanup$),
)
.subscribe(console.log);
action$.next(1);
action$.next(2);
I'm not using real redux-observable actions but I hope you get the point.
Live demo (open console): https://stackblitz.com/edit/rxjs5-9iogn1?file=index.ts
I need to dispatch some actions in some order using redux-observable however, it takes just last action to dispatch. Please see example:
export const fetchClientsEpic = (action$, { dispatch }) =>
action$
.ofType(fetchClients)
.mapTo(fetchClientsPending(true))
.mergeMap(() => {
return ajax
.getJSON('some/get/clients/api')
.map((clients: IClient[]) => {
return fetchClientsSuccess(
map(clients, (client, index) => ({
key: index,
...client,
})),
);
});
});
fetchClientsSuccess is dispatched with clients but fetchClientsPending not, I totally do not get it why. I could use dispatch because I get it in params, but I feel it is not good solution(?). It should be done in the stream I guess. I am starting with RxJs and redux-observable. Is it possible to do?
Operators are chains of Observables where the input of one stream is the output of another. So when you use mapTo you're mapping one action to the other. But then your mergeMap maps that Pending action and maps it to that other inner Observable that does the ajax and such, effectively throwing the Pending action away. So think of RxJS as a series of pipes where data flows through (a stream)
While there is no silver bullet, in this particular case what you want to achieve can be done by using startWith at the end of your inner Observable
export const fetchClientsEpic = (action$, { dispatch }) =>
action$
.ofType(fetchClients)
.mergeMap(() => {
return ajax
.getJSON('some/get/clients/api')
.map((clients: IClient[]) => {
return fetchClientsSuccess(
map(clients, (client, index) => ({
key: index,
...client,
})),
);
})
.startWith(fetchClientsPending(true)); // <------- like so
});
This is in fact the same thing as using concat with of(action) first, just shorthand.
export const fetchClientsEpic = (action$, { dispatch }) =>
action$
.ofType(fetchClients)
.mergeMap(() => {
return Observable.concat(
Observable.of(fetchClientsPending(true)),
ajax
.getJSON('some/get/clients/api')
.map((clients: IClient[]) => {
return fetchClientsSuccess(
map(clients, (client, index) => ({
key: index,
...client,
})),
);
})
);
});
That said, I would recommend against synchronously dispatching another action to set the state that fetching is pending and instead rely on the original fetchClients action itself for the same effect. It should be assumed by your reducers that if such an action is seen, that some how the fetching still start regardless. This saves you the boilerplate and helps a bit on micro-perf since you don't need to run through the reducers, epics, and rerender twice.
There's no rules though, so if you feel strongly about this, go for it :)
I've used redux saga before but i'm still fairly new to it. Anyways, I seem to be running into a problem in the code below.
At the top, you will see the action creator I am using to fire off this AJAX request. Redux is properly dispatching this action and is logging the LOAD_USER_REQUEST type in my console however the function chain stops there. In the code below, you will see that LOAD_USER_REQUEST should call the loadUserDetails generator which should then call the userLogin with the payload received in my action creator.
Let me know if I can supply any additional info that may help. Thanks in advance :)
// Action Creator for LOAD_USER_REQUEST.
export const getExistingUser = ({email = 'tt#gmail.com', password = '12345'} = {}) => ({
type: LOAD_USER_REQUEST,
payload: {email, password}
})
// API call being used in loadUserDetails Saga
export const userLogin = ({email = 'tt#gmail.com', password = '12345'} = {}) => {
return axios.post(`${API}auth/login`, {
email,
password
})
.then(res => {
console.log(res);
localStorage.setItem('token', res.data.token);
let user = res.data.user;
console.log(user);
return user;
})
.catch(err => new Error('userLogin err', err));
}
// Sagas
// loadUserDetails Saga - Should call fn above userLogin with payload from action creator
function* loadUserDetails(payload) {
const user = yield call(userLogin(payload));
yield put({type: LOAD_USER_SUCCESS, user}); // Yields effect to the reducer specifying the action type and user details
}
export function* watchRequest() {
yield* takeLatest(LOAD_USER_REQUEST, loadUserDetails);
}
At first, does your entry point to saga configured well? You should add saga-middleware in store creation, and don't forget to invoke saga process manager by runSaga method.
At second, why you re-delegate generator instance to up-level? Maybe it's meant to yield takeLatest(LOAD_USER_REQUEST, loadUserDetails); without yield* operator? Is has quite different semantics.
At third, by API reference, call effect takes function or generator reference, but you provide promise object. Maybe it's meant const user = yield call(() => userLogin(payload)); or const user = yield call(userLogin, payload);?
I want to use only React, React Redux, React Router and Redux Thunk.
I want to navigate to a dashboard page when a successful user creation action is dispatched. Here is my async action creator,
export function createUser() {
return dispatch => {
dispatch(creatingUser());
return axios.post('/api/users').then(() => {
// how to navigate to dashboard after this action is dispatched?
dispatch(createdUser());
});
};
}
Can you show me exactly where is the place I should naviage programmatically?
Initially looking, I would hope that "createdUser" returns a promise (like #idbehold asked previously)
in a nutshell, something like this.
// make sure your function createdUser returns a promise
function createdUser() {
return new Promise((resolve, reject) => {
//simulate some api request
setTimeout( () =>{
// api resolves. in the .then() do:
resolve()
}, 4000)
})
}
// the dispatch will forward resolution of the promise ahead into the
// .then.. then you can redirect.
dispatch(createdUser()).then( ()=> {
console.log("NAVIGATING AWAY")
//browserHistory.push('/some/path')
//assuming you are importing browserHistory
})
I hope I was helpful, if not :-( , perhaps I didn't fully understand what your need is/was. Let me know, and I'll try to help further.