Redux Toolkit: Async Dispatch won't work in react-native - 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 :)

Related

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

Globally Accessible Component Instance

In our production applications with Vue 2.x, we have a toast component. This toast component is mounted once via a plugin (code below) and is then added to the Vue prototype making it accessible in every component instance.
This makes life a lot easier instead of having to add the toast to everywhere we use.
Vue 2.x plugin
export default {
install(vue: any, _: any) {
const root = new Vue({ render: (createElement) => createElement(Toast) });
root.$mount(document.body.appendChild(document.createElement("div")));
const toastInstance: Toast = root.$children[0] as Toast;
vue.prototype.$toast = {
show: (state: ToastState, text: string) => { toastInstance.show(state, text); },
hide: () => { toastInstance.hide(); }
};
}
Which can then be called in any component like:
this.$toast.show(ToastStates.SUCCESS, "Some success message");
I have recently started another project and would like to do something similar, except using Vue 3. Because we don't have access to this in the setup function, I can't use the same approach as before.
I have been looking into a few things, and have found a few ways of doing it, but none as a definitive best practice.
Provide / Inject:
This seems the most promising, where I can use
export const appInstance = createApp(App);
then
appInstance.provide("toast", toastComponentInstance)
which I can then inject in any components. The problem with this, is that to get it available in every component, it needs to be attached to the initial app instance, where it hasn't been created yet. Maybe I could manually mount it and pass it in (but that seems like a hack).
Composition:
I have also looked at this issue here: How to access root context from a composition function in Vue Composition API / Vue 3.0 + TypeScript? but didn't find that very useful and I had to do all types of hacks to actually gain access to the plugin. Gross code below..
export function useToast() {
const root = getCurrentInstance();
const openToast: (options: ToastOptions) => void = (options: ToastOptions) => {
root.ctz.$toast.open(options);
}
const closeToast: () => void = () => {
root.ctx.$toast.close();
}
return {
openToast,
closeToast
}
}
I have other ideas but they seem far fetched an hacky. Keen to hear peoples thoughts on other solutions. I just want a simple way to have 1 instance of a toast, that I can call two functions on to open / close it when and where I want.
This is roughly how I'd do it...
I'd use Composition API, because it makes passing around internals easy
(I'm using popup instead of toast for simplicity)
myPopup.vue
// internal
const popupMessage = Vue.ref('');
const popupVisible = Vue.ref(true);
// external
export const popUpShow = function(message) {
popupMessage.value = message
popupVisible.value = true
}
export const popupHide = function () {
popupVisible.value = false
}
export default {
setup(){
return {
popupMessage, popupVisible, popupHide
}
}
}
Some component, anywhere, composition or class based...
import { popUpShow } from "./myPopup";
export default {
methods: {
myTriggeredEvent() {
popUpShow("I am your Liter")
}
}
}
By exposing popUpShow, which acts as a singleton, you can import that from anywhere, and not have to worry about context.
There the drawback in using this kind of setup/architecture is that it doesn't scale well. The problem happens if your architecture reaches a certain size, and you have multiple triggers coming from various sources that the component needs to have complex logic to handle its state (not likely for this example though). In that case, a managed global store, ie. Vuex, might be a better choice.

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

Why I can't read a state in react native?

I have the following code:
export default class Testing extends Component {
state = ({
data: []
});
componentDidMount() {
this.setState({
data: this.props.values
});
console.log(this.state.posts); //prints empty but if I do
console.log(this.props.values); //prints the array correctly
}
Where is the error since I can print the props not the state?
Thanks
You're not storing anything in this.state.posts. Your initial state only contains data.
Also when you construct your initial state you should do it like this:
state = {
data: []
}
You do not need the ( ) around it.
If you are wanting to print a value from state as soon as you have stored it you must use the callback functionality of state. This is due to the fact that setState is asynchronous and takes time to set the value. Currently you are trying to read the value before it has been set, use the callback functionality like below.
this.setState({
data: this.props.values
}, () => console.log(this.state.data));
Here are some great articles on setState.
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6
you don't need the ( ) when you set the initial state because it is an object.
export default class Testing extends Component {
state = { //remove (
data: []
}; //remove )
Also worth noting, setState is an async function. You will not be able to getState directly after setState.
In order to get the state right away, you would provide a callback to setState() https://reactjs.org/docs/react-component.html#setstate