How do I access rootGetters from a different namespaced module in Vuex? - vue.js

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))
})
}

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 :)

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.

How to access dispatch function from epic in redux-observables

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 });
});

Initializing a map in firestore

I'm trying to build an app using react native with a firestore database. I'm fairly new to the react native framework (as well as working with firestore), so it's possible I might be trying to solve this problem the wrong way.
I have a database that works well and is already populated. For each user of this app, I'd like to add a map to their entry. I want to use this map to store some data about the user which they can fill out later.
Here's some code:
componentDidMount() {
this.readProfile(this.props.uid);
}
readProfile = (uid) => {
this.props.getProfile(uid).then((profile) =>
{
if(!profile.userMap)
{
profile.userMap = generateUserMap();
}
...
}
export const generateUserMap = function () {
var map = new Map();
SomeEnum.forEach((key, value) => {
map.set(key, false);
});
AnotherEnum.forEach((key, value) => {
map.set(key, false);
});
OneMoreEnum.forEach((key, value) => {
map.set(key, false);
});
return map;
};
...
<Input
value={this.state.profile.userMap[SomeEnum.Foo]}
onChangeText={(foo) => this.updateUserMap({ foo })}
/>
What I want this code to be doing is to read in the user's profile when I load the page. That part seems to be working fine. My next concern is to properly initialize the map object. The code doesn't seem to be properly initializing the map, but I'm not sure why. Here's why I say that:
TypeError: Cannot read property 'Foo' of undefined
With the stack trace pointing to my component's Connect() method.
Any help would be greatly appreciated.
EDIT: Apologies for the oversight, here is the updateUserMap function:
updateUserMap = (property) => {
const profile = Object.assign({}, this.state.profile, property);
this.setState({ profile });
}
So, as anyone who looks over this question can probably tell, I was doing a few things pretty wrong.
The error I'm getting referred specifically to that input block in my render method - this.state.profile.userMap was undefined at that point. I can guarantee that it won't be undefined if I do my check within the render method but before I'm accessing the userMap. Because of how the lifecycle methods work in react native, ComponentDidMount wouldn't be called before my render method would.
My enum code also wouldn't work. I changed that to a simple for loop and it works like a charm.
Here's my updated code:
render() {
if(!this.state.profile.userMap)
{
this.state.profile.userMap = generateUserMap();
}

Vuex State - Array empty

My Problem is, that the state-variable "genreRankings" in "store.js" is never updating.
Can somebody tell me why?
I'm accessing the Store via my Component as follows:
saveMovie (item) {
this.$store.dispatch('addMovie', item).then(x => {
console.log(this.$store.state.savedMovies)
this.$store.commit('update_genreRankings', Util.getGenreRankings(this.$store.getters.savedMovies))
})
},
removeMovie (item) {
this.$store.dispatch('removeMovie', item).then(x => {
this.$store.commit('update_genreRankings', Util.getGenreRankings(this.$store.getters.savedMovies))
})
},
Here is store.js (https://gist.github.com/oaltena/ccc70c06c29a1d9af6aa3234aba79518) and Util.js (https://gist.github.com/oaltena/67b8431199e9a6d74681c04d9183e630).
When i access the "genreRankings" via VueDevTools the array is always empty.
Help, please! :-)
Try "replacing" the state with a new array :
state.savedMovies = state.savedMovies.concat(object)
As written in the Vuex documentation, the state of Vuex store follows the same rules as the state in the components : https://vuex.vuejs.org/guide/mutations.html#mutations-follow-vue-s-reactivity-rules
PS: it's pretty ugly to call mutations directly from the components, use mapActions to map your actions in your components, then call commit from the action. You'll make a more maintenable code.
Try replacing this:
update_genreRankings (state, object) {
state.genreRankings = object
}
with this:
update_genreRankings (state, object) {
Vue.set(state, 'genreRankings', object)
}
Reference: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats