Returning Promise from action creator in React Native using redux-thunk - react-native

I have an action creator that is called from my React component:
// ...
import { connect } from 'react-redux';
// ...
import { submitProfile } from '../actions/index';
// ...
onSubmit() {
const profile = {
name: this.state.name
// ...
};
this.props.submitProfile(profile)
.then(() => { // I keep getting an error here saying cannot read property 'then' of undefined...
console.log("Profile submitted. Redirecting to another scene.");
this.props.navigator.push({ ... });
});
}
export default connect(mapStateToProps, { submitProfile })(MyComponent);
The definition of the action creator is something like the following. Note I am using the redux-thunk middleware.
export function submitProfile(profile) {
return dispatch => {
axios.post(`some_url`, profile)
.then(response => {
console.log("Profile submission request was successful!");
dispatch({ ... }); // dispatch some action
// this doesn't seem to do anything . . .
return Promise.resolve();
})
.catch(error => {
console.log(error.response.data.error);
});
};
}
What I want to be able to do is call the action creator to submit the profile and then after that request was successful, push a new route into the navigator from my component. I just want to be able to determine that the post request was successful so I can push the route; otherwise, I would not push anything, but say an error occurred, try again.
I looked up online and found Promise.resolve(), but it doesn't not seem to solve my problem. I know that I could just do a .then after calling an action creator if I was using the redux-promise middleware. How do I do it with redux-thunk?

The return value from the function defined as the thunk will be returned. So the axios request must be returned from the thunk in order for things to work out properly.
export function submitProfile(profile) {
return dispatch => {
return axios.post(`some_url`, profile) // don't forget the return here
.then(response => {
console.log("Profile submission request was successful!");
dispatch({ ... }); // dispatch some action
return Promise.resolve();
})
.catch(error => {
console.log(error.response.data.error);
});
};
}

Related

ASync/Await is not working as expected in router.BeforeEach guard in vue?

this is my router guard :
router.beforeEach(async (to,from,next)=>{
await store.dispatch('GetPermission');
if(to.matched.some(record => record.meta.requireAuth)){
let permissions=store.state.permissions; //getting empty
console.log(permissions);
if(permissions.filter(per => (per.name === 'read_list').length!=0)){
next({
path:'/dashboard/create'
})
}
else{
next()
}
}
// else if(to.matched.some(record => record.meta.requireAuth)){
// if(store.token!=null){
// next({
// path:'/dashboard'
// })
// }
// else{
// next()
// }
// }
else{
next()
}
});
problem is here though i m using await in dispatch method , i m not getting state value of permissions which is initially empty
here is vuex store code :
GetPermission(context){
axios.defaults.headers.common['Authorization']='Bearer ' + context.state.token
axios.get('http://127.0.0.1:8000/api/user').then((response)=>{
console.log(response)
context.commit('Permissions',response.data.permission)
})
//mutation:
Permissions(state,payload){
state.permissions=payload
}
//state
state:{
error:'',
token:localStorage.getItem('token') || null,
permissions:'',
success:'',
isLoggedin:'',
LoggedUser:{}
}
help me to solve it please ??
actions in Vuex are asynchronous. The only way to let the calling function (initiator of action) to know that an action is complete - is by returning a Promise and resolving it later.
Here is an example: myAction returns a Promise, makes a http call and resolves or rejects the Promise later - all asynchronously
actions: {
myAction(context, data) {
return new Promise((resolve, reject) => {
// Do something here... lets say, a http call using vue-resource
this.$http("/api/something").then(response => {
// http success, call the mutator and change something in state
resolve(response); // Let the calling function know that http is done. You may send some data back
}, error => {
// http failed, let the calling function know that action did not work out
reject(error);
})
})
}
}
Now, when your Vue component initiates myAction, it will get this Promise object and can know whether it succeeded or not. Here is some sample code for the Vue component:
export default {
mounted: function() {
// This component just got created. Lets fetch some data here using an action
this.$store.dispatch("myAction").then(response => {
console.log("Got some data, now lets show something in this component")
}, error => {
console.error("Got nothing from server. Prompt user to check internet connection and try again")
})
}
}
Also,you are calling same route when no permission match, in that case it always call your same route and make infinite loop.
Redirect to access denied page if permission denied.

How can i pass parameters from method to action in vuejs vuex

I have to files with this code:
Users.vue
methods: {
obtenerUsuarios() {
console.log('Obtener Usuarios')
this.$store
.dispatch('auth/getValidToken')
.then((data) => {
console.log(data). // Console First Message
this.$store
.dispatch('user/fetchUsers', data)
.then((response) => {
this.items = response.data
})
.catch((error) => {
console.log(error)
})
})
.catch((error) => {
console.log('Error: ' + error)
})
},
},
Console Firsts Mesagge show me a json web token in console that is ok.
When i dispatch 'user/fetchUsers in
user.js
export const actions = {
fetchUsers({ jwt }) {
console.log('Action JWT:' + jwt) //Second console.log
return UserService.getUsers(jwt)
},
}
The second messaje show me: Action JWT:undefined in the console
if i change the line two to
fetchUsers(jwt) {
The second messaje show me: Action JwT:[object Object]
I need to pass a json web token from Users.vue method to fetchUsers action y user.js
I will to appreciate any help
Jose Rodriguez
Your action method currently declares the data in the first argument (and no second argument), but actions receive the Vuex context as its first argument. The data payload is in the second argument:
const actions = {
//fetchUsers(data) {} // DON'T DO THIS (1st arg is for context)
fetchUsers(context, data) {}
}

how to pass a reference to a component when calling a vuex action

I'm fairly new to vue (and very new to vuex). I would like to move some axios api calls to be actions in my Vuex store. I know have for example:
actions:{
LOAD_USER: function ({ commit }) {
axios.get('/arc/api/v1/me', {dataType: 'json'})
.then((response )=> {
commit('SET_USER', { user: response.data.user })
})
.catch(function (error) {
console.log(error.message);
});
and call this in my calling component via:
this.$store.dispatch('LOAD_USER')
and this is working. My problem is that I need to set some variables in the calling component to false or kill a progress bar. Here's what I was previously using in my calling component:
this.loading = true
this.$Progress.start()
axios.get('/arc/api/v1/me', {dataType: 'json'})
.then((response )=> {
this.$Progress.finish()
this.loading = false
this.$store.state.user = response.data.user;
this.user = this.$store.state.user
})
.catch(function (error) {
this.$Progress.fail()
console.log(error.message);
});
How would I integrate these loading behaviors into my vuex action? How would I pass a reference to my component via this call:
this.$store.dispatch('LOAD_USER')
or is there a better solution?
Well, you can always use the second parameter of Store.dispatch() to pass any payload into the corresponding action:
this.$store.dispatch('LOAD_USER', this); // passing reference as payload
... but I strongly recommend against doing this. Instead, I'd rather have the whole state (including 'loading' flag, etc.) processed by VueX.
In this case, a single action - LOAD_USER, based on asynchronous API request - would commit two mutations to Store: the first one sets loading flag when the request has been started, the second one resets it back to false - and loads the user data. For example:
LOAD_USER: function ({ commit }) {
commit('LOADING_STARTED'); // sets loading to true
axios.get('/arc/api/v1/me', {dataType: 'json'})
.then(response => {
commit('LOADING_COMPLETE'); // resets loading flag
commit('SET_USER', { user: response.data.user });
})
.catch(error => {
commit('LOADING_ERROR', { error }); // resets loading
console.log(error.message);
});
This approach, among the other advantages, simplifies things a lot when your requests' logic gets more complicated - with error handling, retries etc.
Actions can return a promise https://vuex.vuejs.org/en/actions.html
I think what you want to do is activate the loading when you call your action and stop the loading when the promise is resolved or rejected.
// Action which returns a promise.
actions: {
LOAD_USER ({ commit }) {
return new Promise((resolve, reject) => {
axios.get('/arc/api/v1/me', {dataType: 'json'})
.then((response )=> {
commit('SET_USER', { user: response.data.user })
resolve()
})
.catch(function (error) {
console.log(error.message);
reject(error);
});
})
}
}
// Update loading when the action is resolved.
this.loading = true;
store.dispatch('LOAD_USER').then(() => {
this.loading = false;
})
.catch(function(error) {
// When the promise is rejected
console.log(error);
this.loading = false;
});
If you can't achieve your goal using the above you can add the loading boolean to your vuex store and import it in your component. Than modify the loading boolean inside your action (using mutations) to let the view update.
Note: I would not pass a reference to your actions. While this is possible there are likely better solutions to solve your problem. try to keep the view logic in your components whenever possible.

React redux async data sync then re-render view

I have a number of actions and reducers setup for different content types, e.g. pages, events and venues. These actions and reducers get data which has been saved to AsyncStorage, by another action called sync, and puts it into the store.
Sync performs an async call to Contentful and retrieves any new/updated/deleted entries, which I then save to AsyncStorage.
What is the best way to ensure the view correctly is re-rendered after the async call is finished?
Should syncReducer merge data into the store that would normally be pulled out by pagesReducer, venuesReducer etc or should there be some kind of event emitted after syncReducer is done?
Data is pulled in asynchronously for offline viewing and keeping things fast, so I really don't want to wait for the sync before rendering.
data/sync.js
import { AsyncStorage } from 'react-native';
import database from './database';
const cache = {
getByType: async (query) => {
return new Promise(async(resolve, reject) => {
// Get results from AsyncStorage
resolve(results);
});
},
sync: async () => {
return new Promise(async(resolve, reject) => {
database
.sync(options)
.then(async results => {
// Save results to AsyncStorage
resolve(results);
});
});
}
};
export default cache;
actions/sync.js
import actionTypes from '../constants/actionTypes';
import cache from '../data/cache';
export function sync() {
return dispatch => {
dispatch(syncRequestedAction());
return cache
.sync()
.then(() => {
dispatch(syncFulfilledAction());
})
.catch(error => {
console.log(error);
dispatch(syncRejectedAction());
});
};
}
function syncRequestedAction() {
return {
type: actionTypes.SyncRequested
};
}
function syncRejectedAction() {
return {
type: actionTypes.SyncRejected
};
}
function syncFulfilledAction(data) {
return {
type: actionTypes.SyncFulfilled,
data
};
}
actions/getPages.js
import actionTypes from '../constants/actionTypes';
import cache from '../data/cache';
export function getPages() {
return dispatch => {
dispatch(getPagesRequestedAction());
return cache
.getByType('page')
.then(results => {
dispatch(getPagesFulfilledAction(results));
})
.catch(error => {
console.log(error);
dispatch(getPagesRejectedAction());
});
};
}
function getPagesRequestedAction() {
return {
type: actionTypes.GetPagesRequested
};
}
function getPagesRejectedAction() {
return {
type: actionTypes.GetPagesRejected
};
}
function getPagesFulfilledAction(settings) {
return {
type: actionTypes.GetPagesFulfilled,
pages
};
}
reducers/pagesReducer.js
import { merge } from 'lodash';
import actionTypes from '../constants/actionTypes';
const pagesReducer = (state = {}, action) => {
switch (action.type) {
case actionTypes.GetPagesRequested: {
return merge({}, state, { loading: true });
}
case actionTypes.GetPagesRejected: {
return merge({}, state, { error: 'Error getting pages', loading: false });
}
case actionTypes.GetPagesFulfilled: {
const merged = merge({}, state, { error: false, loading: false });
return { ...merged, data: action.pages };
}
default:
return state;
}
};
export default pagesReducer;
In the end I was able to solve this by importing the other actions into my sync action, and dispatching depending on which data needs to be updated.
import { getEvents } from './getEvents';
import { getPages } from './getPages';
import { getVenues } from './getVenues';
export function sync() {
return dispatch => {
dispatch(syncRequestedAction());
return cache
.sync()
.then(results => {
dispatch(syncFulfilledAction());
if (results.includes('event')) {
dispatch(getEvents());
}
if (results.includes('page')) {
dispatch(getPages());
}
if (results.includes('venue')) {
dispatch(getSettings());
}
})
.catch(error => {
console.log(error);
dispatch(syncRejectedAction());
});
};
}
Your sync action should be a thunk function (redux middleware) that makes the call to Contentful, resolves the promise, and contains the data, or error. Then you can dispatch another action, or actions to reduce the data into the store.
On each component that you want to re-render (based on the data being updated in the store via the actions we just dispatched and reduced), if you have connect(mapStateToProps, mapDispatchToProps) and have included those parts of the store in MSTP, those props will be updated which will re-render the components.
You can even be more explicit about the resolution of data if necessary by creating another action where you can dispatch and reduce to some part of your store the current state of the fetch.
So when you make the call, you could dispatch 'FETCH_IN_PROGRESS', then either 'FETCH_ERROR' or 'FETCH_SUCCESS' and if that was mapStateToProps into your component, you could choose to evaluate it in shouldComponentUpdate() and based on where in the process it is, you could either return true or false based on if you wanted to rerender. You could also force render in componentWillReceiveProps. I'd start with just relying on props changing and adding this if necessary.
You should use Redux Persist for this kind of thing, it supports AsyncStorage and a range of other options.
https://github.com/rt2zz/redux-persist
Actions and Reducers should be just designed to update the Redux store. Any other action is known as a side effect, and should be managed in a Middleware or Store Enhancer.
I would strongly advise against using Redux-Thunk it is way too powerful for the few things that it is useful for and very easy to create unmaintainable anti-patten code as it blurs the boundaries between actions and middleware code.
If you think you need to use Redux-Thunk first look to see if their is already a middleware that does what you need and if not learn about Redux-Sagas.

Execute custom functions one after another - Callback logic in Vue.js

There is a form which submits some data to an API in my component. Assume that it's method is ProcessLogin(). Inside this function I have written my API calls using axios. With the help of then() I have handled my server response and displayed my toast. All good.
Now as a part of my code clean up, I have decided to move all my axios functions to another api.js file and export functions from there. Here is an example function I have in my api.js file :
function ApiLogin(data) {
const url = `${BASE_URL}/authenticate`;
axios.post(url,data).then(response => {
return response;
}).catch(error => {
return error.response;
});
}
On the other side in my component I have my method defined as below :
methods: {
ProcessLogin() {
var status = ApiLogin(this.data);
console.log(status);
}
}
When executing this, I get undefined on my console. I know why it is happening. Because console.log(status) executes before ApiLogin could process and sends it's response. How to handle this kind of situation.? I know that callback is the rescue here, but I am not really sure about how to integrate it.
If you return the axios call from your ApiLogin function:
function ApiLogin(data) {
const url = `${BASE_URL}/authenticate`
return axios.post(url, data)
}
You could then handle the response in your component using then and console log from there:
methods: {
ProcessLogin() {
ApiLogin(this.data)
.then(res => console.log(res))
.catch(err => console.log(err))
}
}
...or with async/await:
methods: {
ProcessLogin: async function() {
try {
var status = await ApiLogin(this.data)
console.log(status)
}
catch(err) {
console.log(err)
}
}
}