How to access dispatch function from epic in redux-observables - redux-observable

I'd like to know if there's anyway to access redux's dispatch function from an epic in redux-observables (1.2).
export const epicDownloadProfile = (action$, { dispatch }) =>
action$.pipe(
ofType(DOWNLOAD_INIT.getType()),
switchMap(() =>
from(downloadStart(dispatch)).pipe(
map(() => DOWNLOAD_INIT()),
catchError(err => of(DOWNLOAD_ERROR.asError(err.message)))
)
)
)
I know this is not ideal, but I have a very complex function that makes a lot of things while downloading, so I'd need to pass dispatch to downloadStart().
Redux-observables provides me with a StateObservable object as the second parameter of the epic, it does contain the state, but it does not contain the dispatch function... In the example { dispatch } comes undefined. Is there any other way I can access it?

You did mention this isn't ideal, but for others who might not read your question I must add a warning that doing this is suggestive that what you might be doing is an anti-pattern--but not always! Certainly if you're using some sort of third party library that you have no control over, and you need to pass it to it, that's an understandable workaround. Just don't be too tempted to called store.dispatch() around your Epics all the time, as it is a usually a sign you're fighting redux-observable. Of course, at the end of the day, this is just advice hehe :)
OK. So here's how you can do it:
redux-observable provides a way to inject dependencies into every epic. So when you create your epicMiddleware, you can pass a reference to the store, dispatch, or anything else.
https://redux-observable.js.org/docs/recipes/InjectingDependenciesIntoEpics.html
/* Where ever you create your store/middleware
*****************************************/
const middlewares = [];
const epicMiddleware = createEpicMiddleware({
dependencies: {
get store() { // or getStore() if you want
return store;
}
}
});
middlewares.push(applyMiddleware(epicMiddleware));
const store = createStore(
rootReducer,
initialState,
composeEnhancers(...middlewares)
);
epicMiddleware.run(rootEpic);
/* Where ever this epic is
*************************/
const epicDownloadProfile = (action$, state$, { store }) =>
action$.pipe( dependencies ----^
ofType(DOWNLOAD_INIT.getType()),
switchMap(() =>
from(downloadStart(store.dispatch)).pipe(
map(() => DOWNLOAD_INIT()),
catchError((err) => of(DOWNLOAD_ERROR.asError(err.message)))
)
)
);
There are other approaches too, such as exporting your store from the module, importing it inside your epic modules. But that might not be good if you need to don't want your store to be a singleton, doing SSR, etc.
Here's another approach, if you prefer it, since you should always start the root epic after the store has been created anyway.
// Manually inject it yourself by wrapping the "root epic"
// with another function, which is basically an epic which
// defers to your root epic.
epicMiddleware.run((action$, state$) => {
return rootEpic(action$, state$, { store });
});

Related

Redux Toolkit: Async Dispatch won't work in react-native

I'm trying to make some async actions with redux toolkit in react-native. The project runs on redux without any issues, beside the implementation issues for createAsyncThunk.
I used the same logic as described in the docs
Within my Slice, I'm creating the createAsyncThunk Object as follows:
export const fetchAddressList = createAsyncThunk('/users/fetchAddresses', async(thunkAPI) => {
const state = thunkAPI.getState();
console.log("THUNK state.loggedIn: "+state.loggedIn);
if(state.loggedIn){
return apiHelper.getAddressDataAsync();
}
});
It only differs in the export tag before const tag compared to the docs. I had to make it in order to access the fetchAddressList from outside. The apiHelper.getAddressDataAsync() is an async method, that returns the result of a fetch.
Than I added the extraReducers attribute to my slice object.
export const appDataSlice = createSlice({
name: "appDataReducer",
initialState:{
//Some initial variables.
},
reducers: {
//Reducers...
},
extraReducers: (builder) => {
builder.addCase(fetchAddressList.fulfilled, (state, action) => {
console.log("FULLFILLED::: ",action.payload);
state.addressList = action.payload.addressList;
state.defaultAddressId = action.payload.defaultAddressId;
})
}
});
export const { /*REDUCER_METHOD_NAMES*/ } = appDataSlice.actions;
This slice is stored in the store using configureStore, among other slices, that are definitely working fine.
Calling the fetchAddressList() method using dispatch doesn't do anything:
dispatch(fetchAddressList());
What exactly am I doing wrong here? Would appreciate any hints.
Edit:
Are there configurations required within the configureStore()-method when creating the store object?
This is how I create the store object:
export const store = configureStore({
reducer: {
/*Other reducer objects....,*/
appDataReducer: appDataSlice.reducer
},
});
Maybe something is missing here...
It was due to wrong usage of the createAsyncThunk()-method. I'd passed the thunkAPI to be as the first (and only) parameter to the inner method, which was linked to user arguments passed through parameters into the initial dispatch method (like dispatch(fetchAddressList("ARG_PASSED_TO_FIRST_PARAMETER_OF_ASNYCTHUNK"));). However thunkAPI is being injected into the second parameter of createAsyncThunk()-method and as a result thunkAPI was undefined, since I hadn't passed any parameters by calling dispatch(fetchAddressList());
It was odd, to not have any errors / exceptions
calling a method of an undefined object though => thunkAPI.getState().
The solution is to use the second parameter for thunkAPI.
You do have two options by doing so.
1 - Either load the whole thunkAPI into the second parameter and use it as so:
export const fetchAddressList = createAsyncThunk('/users/fetchAddresses', async(args, thunkAPI) => {
console.log("TEST: ", thunkAPI.getState());
thunkAPI.dispatch(...);
});
2 - Or load exported methods by the thunkAPI:
export const fetchAddressList = createAsyncThunk('/users/fetchAddresses', async(args,{getState, dispatch}) => {
console.log("TEST: ", getState());
dispatch(...);
});
Both ways will work. Happy coding :)

How do I access rootGetters from a different namespaced module in Vuex?

I have a Vuex module named 'forms'. I have a different (also namespaced) module named 'users'.
I'm using Vuexfire (for the first time, which I think is what's tripping me up). And have an action that works like this:
const actions = {
loadPendingHoursRequests: firestoreAction((context) => {
context.bindFirestoreRef('pendingHoursRequests', db.collection('hours')
.where('submittedToUID', '==', "iTd865JKWXRmhz2D2mtW7KIpL7a2"))
}),
This works as expected and creates a real-time connection between Firestore and Vuex. The problem is I want "iTd865JKWXRmhz2D2mtW7KIpL7a2" to be a dynamic value drawn from the 'users' module.
I'm just completely lost. If I refactor like this:
loadPendingHoursRequests ({ dispatch, commit, getters, rootGetters }) {
let uid = rootGetters['users/currentUserUID'];
console.log(uid)
firestoreAction((context) => {
context.bindFirestoreRef('pendingHoursRequests', db.collection('hours').where('submittedToUID', '==', uid))
})
}
The console.log above returns 'undefined'. And even if I remove the .where('submittedToUID', '==', uid), the firestoreAction doesn't work anyway.
Thanks in advance. I'd love to know what I'm not understanding here.
Untested (I don't use VuexFire) but assuming the bindFirestoreRef needs the context object, you can access rootGetters as a property of it as well. Putting the two snippets together ilke this:
const actions = {
loadPendingHoursRequests: firestoreAction((context) => {
const uid = context.rootGetters['users/currentUserUID'];
context.bindFirestoreRef('pendingHoursRequests', db.collection('hours')
.where('submittedToUID', '==', uid))
})
}

Can I create a mobx computed inside a React render function to use like useMemo()?

I'm wondering how to go about using a mobx observable inside a useMemo hook. I know I could pass all possibly dependencies to the hook, but that could get kind of messy:
const MyComponent = observer(() => {
const people = useGetPeople();
const peopleFormatted = useMemo(() => {
return people.map(person => person.fullName);
},[ ...? ]);
});
I can't easily make every person's firstName be a dependency of useMemo. I'd think I could extract the functionality to a computed ... but I feel like this won't work:
const MyComponent = observer(() => {
const people = useGetPeople();
const peopleFormatted = computed(() => {
return people.map(person => person.fullName);
});
});
I feel like it will confuse mobx to create a computed inside a reaction that the reaction must depend on.
I know I could extract the computed to each person but I don't feel like that's a solution that matches every use case.
Thanks in advance!
Assuming const people = useGetPeople(); is an observable array of some sort of people objects...
const peopleFormatted = computed(() => {
return people.map(person => person.fullName);
}).get(); //note .get()
Should work fine inside the observer function body. See https://mobx.js.org/computeds-with-args.html#2-close-over-the-arguments
What is confusing me is useGetPeople();
That typically means you are using react's state api for managing state and reactions. ie: useState, etc.
Without seeing what useGetPeople() does under the hood, it's hard to give a concrete answer.

Redux Observables: Separate epics for same actions but different filters?

I'm new to redux observables: https://github.com/redux-observable/redux-observable
I have a simple use case where I want to do 2 different things based on the user's signedIn state.
If signed in, add rsvp
If not signed in, show sign in modal
I have this in my app/redux/epics/addRSVP.js file:
import 'rxjs';
import * as scheduleActions from '../ducks/schedule';
export default function searchUsers(action$, store) {
return action$.ofType(scheduleActions.ADD_RSVP)
.filter(() => store.getState().user.signedIn)
.map(action => scheduleActions.rsvpAdded(action.payload));
};
My question is, should I be creating another app/redux/epics/preventRSVPIfNotSignedIn.js epic for the signed out use case? Something like:
import 'rxjs';
import * as authenticationActions from '../ducks/authentication';
export default function searchUsers(action$, store) {
return action$.ofType(scheduleActions.ADD_RSVP)
.filter(() => !store.getState().user.signedIn)
.map(action => authenticationActions.show());
};
or is there a way I can put both in the same file? I feel like it would end up being a lot of epics if it's the former. Would be great to know what the general convention is.
Sebastian gave great advice, and generally I would split them up and duplicate the logic. However, if you really do this a lot, you can create your own abstractions so you don't need to repeat yourself.
You could either create a helper like requireAuth that takes your expected action and an epic that is only supposed to receive those actions when they have valid auth. It would then return a new epic that wraps it.
// Helper to abstract the common auth requirement checks
// which will also dispatch authenticationActions.show()
// when they attempt to do something they can't
const requireAuth = (type, epic) => (action$, store) => {
// matching actions which they have permission for
const valid$ = action$
.ofType(type)
.filter(() => store.getState().user.signedIn);
// matching actions they do NOT have permission for
const invalid$ = action$
.ofType(type)
.filter(() => !store.getState().user.signedIn);
return Observable.merge(
epic(valid$, store),
invalid$.map(action => authenticationActions.show())
);
};
const searchUsersEpic = requireAuth(scheduleActions.ADD_RSVP, (action$, store) =>
action$.map(action => scheduleActions.rsvpAdded(action.payload))
);
// You can then use requireAuth for any epics that require it
// as an example:
const searchMoviesEpic = requireAuth(scheduleActions.SEARCH_MOVIE, (action$, store) =>
action$.mergeMap(action =>
ajax(`/search/for/the/movie/${action.id}`)
.map(resp => scheduleActions.searchMoviesFulfilled(resp))
)
);
Adjust as needed--but be careful, adding abstractions can make your codebase hard to reason later or introduce bugs when someone later adjusts the abstractions without realizing how it impacts other codepaths. Testing becomes more important!
If you're unsure, always create separate epics. It is easier to test and change later. Plus, there is little to no downside of this approach (performance-wise). Combining two epics is kind of adding an abstraction without knowing if it is really necessary.
Also, by the looks ducks of it, the domain of those two (side) effects are different. I would say this is a strong indicator that using separate epics is a good idea here and will be more future proof.
That said, if you're certain that your epic will not change or get more complex (currently if/else), I guess it is fine too.
After taking a second look, I guess what you want to do is "if the user is not logged in, show im a login page and wait until (s)he is logged in and after a successful login fire the RSVP action". If this is your use case, you might want to look into delayWhen. This could potentially be even a better solution, but it's more of an advanced feature of RxJS. Maybe this is a good task for refactoring when you're more comfortable with redux-observables :)

Redux Observables: General way to return non-observable in mergeMap?

observable.
In my epic, I just want to call a 3rd party library for scheduling a push notification on iOS (I'm using react native):
import 'rxjs';
import { Observable } from 'rxjs';
import PushNotification from 'react-native-push-notification';
import * as calendarActions from '../ducks/calendar';
export default function cancelRSVPIfSignedIn(action$, store) {
return action$.ofType(calendarActions.CANCEL_RSVP)
.filter(() => store.getState().user.signedIn)
.mergeMap(action => {
return new Observable(observer => {
const meetupId = action.payload;
PushNotification.cancelLocalNotifications({ id: meetupId });
observer.next(meetupId);
});
})
.map(action => calendarActions.rsvpAdded(action.payload));
};
This works fine, but I was wondering if this is the most common approach to return an Observable and inside it just to call observer.next()?
If you need to create an Observable that wraps non-Observable code and you want to observe the results of that code--that's the crucial part.
If you don't care about whether the side effect produces anything, errors, or if it completes asynchronously or not, then wrapping it in a custom Observable isn't neccesary. You could just use the .do() operator instead.
export default function cancelRSVPIfSignedIn(action$, store) {
return action$.ofType(calendarActions.CANCEL_RSVP)
.filter(() => store.getState().user.signedIn)
.do(action => PushNotification.cancelLocalNotifications({ id: action.payload }))
.map(action => calendarActions.rsvpAdded(action.payload));
};
There's one missing thing I want to point out in your code, however. You never call observer.complete() which means you're accidentally leaking the subscription to that custom Observable. Every time a new CANCEL_RSVP comes in another one will be created and subscribed to (mergeMap) with the previous sticking around even though it has no work left to be done.
Remember to always call observer.complete() when you're done, unless of course your Observable intentionally never completes. 😄
(also, it emits observer.next(meetupId) but then later .map(action => but that might just be a typo in this question, not your apps code)