Redux, Redux Toolkit how to wait until dispatch is finished - redux

guys
Like in the subject is there a solution to wait until dispatch action is finished and then dispatch another?
Do I need thunk?
dispatch(someAction());
when someAction is finished dispatch anotherAction()
dispatch(anotherAction());

It really depends on the action. Normal non-thunk actions are synchronous, so in the next line after the dispatch, the first action will already be 100% handled.
If you are dispatching thunk actions there, you can either await or .then(..) the value that is returned by dispatch.

To elaborate on the idea of #phry.
I use redux-toolkit and have this as an example thunk:
export const EXAMPLE_THUNK = createAsyncThunk(
"group/event/example_thunk",
async () => {
// Just resolves after 2 seconds.
return await new Promise((resolve) => setTimeout(resolve, 2000));
}
);
To dispatch another action after the first dispatch, you can simply await the first dispatch. In my example I updated the loading state.
Although this example works, I am not 100% sure this is the best design pattern out there. Many examples I have seen is updating the state is some dispatch has been resolved! Personally I think this is a more clear way of writing my code
const handleChangeStatus = async () => {
setIsLoading(true);
await dispatch(EXAMPLE_THUNK())
// Or dispatch something else.
setIsLoading(false);
};

Related

How to re-dispatch action in Saga like redux toolkit Automated Re-fetching

I am trying to implement redux-saga action dispatch functioning like Automated Re-fetching in the redux toolkit.
When opening the app, I dispatch some actions to fetch data from the server and update the redux store. When the user stays background and re-enters the app, depending on how long users stay long inactive, then the app would re-dispatch the action to fetch data from the server again. I can save the timestamp whenever fetching from the server and compare the time of the moment of switching from inactive to active. But if there is a well-already-made feature provided, I would definitely want to use that!
There are some libraries like saga-query that do similar thing for you as rtk-query, but as far as I can tell this lib specifically doesn't support refetch on focus out of the box.
Without any lib, it could be implemented like this:
import {delay, put, call, takeEvery, takeLeading, fork} from 'redux-saga/effects';
// Utility function to create a channel that will receive a message
// every time visibility changes
const createVisibilityChannel = () => {
return eventChannel((emit) => {
const handler = () => void emit(!document.hidden);
document.addEventListener('visibilitychange', handler);
return () => document.removeEventListener('visibilitychange', handler);
});
};
// Works as takeLeading except it ignores actions for extra additional time
const takeLeadingWithDelay = (ms, pattern, saga, ...args) => {
return takeLeading(pattern, function* (action) {
yield call(saga, ...args, action);
yield delay(ms);
});
};
// Root saga
function* appSaga() {
// Creates visbility channel
const visibilityChannel = yield call(createVisibilityChannel);
// Limits time between refetches for 1 minute
yield takeLeadingWithDelay(60000, 'FETCH_DATA', fetchData);
// Dispatches fetch action every time page becomes visible
yield takeEvery(visibilityChannel, function* (visible) {
if (visible) yield put({type: 'FETCH_DATA'});
});
// Fetches data on app start and starts the first timer
yield put({type: 'FETCH_DATA'})
}
// Example fetching function
function* fetchData() {
const response = yield fetch(
'https://api.spacexdata.com/v3/launches?limit=5',
);
const data = yield response.json();
console.log({data});
}
This solution assumes that the delay timer isn't specific to the page blur/focus but any refetches including the one on page focus, since in the opposite case I am not sure what the logic for the timer should be when the user switches to page too early.

Error in epic with redux-observable and rxjs

I'm trying to create an epic that fetches data from firestore using rxfire. On every emission of a value, an action needs to be dispatched. My codes looks like this:
const fetchMenu = (action$, _state$) => {
return action$.pipe(
ofType(Types.FETCH_MENU_REQUESTED),
flatMap((async ({ resID }) => {
const firestore = firebase.firestore();
const menuRef = firestore.collection('menus').where('resID', '==', resID);
return collectionData(menuRef, 'id')
.pipe(
map(val => {
return Creators.fetchMenuSuccess(val);
})
)
}
)),
);
};
However, I'm getting the error Actions must be plain objects. Use custom middleware for async actions.
As far as I understand, the pipe operator is wrapping my value in an observable and that's why I'm getting the error, but I'm not sure what to do so that it only returns the action. I'm still a bit new to rxjs so any help would be very appreciated.
Your flatMap callback is async, which means it's not returning an observable that emits the fetchMenuSuccess action, it's returning a promise that resolves to the observable. Rx will unwrap the promise automatically, which means that the observable returned from this epic emits observables instead of subscribing to them and emitting their own values.
Removing the async keyword should fix it.

async/await with firebase .on() database listener

I'm designing a messaging application with redux as my state manager and firebase to store my data. I've started writing my database listeners in this fashion:
const fetchMessages = roomKey => async dispatch => {
const db = firebase.database();
let { messages } = await db.ref(`messages/${roomKey}`).on('value');
dispatch({
type: SET_MESSAGES,
payload: messages,
})
};
All this basically does is fetch messages by a room key and then dispatch an action that sets the messages in the redux state.
Traditionally, this would be written as such:
db.ref(`messages/${roomKey}`).on('value', snapshot => {
const messages = snapshot.messages;
dispatch({
type: SET_MESSAGES,
payload: messages,
})
});
And everytime something changes in messages/${roomKey}, my dispatch function would be executed. I'm wondering if this will work the same using the async await syntax, and if not, how I could make it work.
Hope this was enough detail!
The reference's on method does not return a promise. The callback it's passed can be invoked multiple times, so a promise does not fit with the method's contract.
However, the reference's once method method does return a promise, as the (optional) callback it's passed is invoked only once - after which the promise resolves. The once method is likely the one you want to use.

Redux timeout thunk error "Actions must be plain objects"

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.

How to navigate at the end of a Redux action?

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.

Resources