VueX/VueJs : Execute code in component after async process - javascript

I'm trying to display a toast when a async request is finished.
I've implemented this process:
Single File Component calls updateUserProfile() actions in my VueX store
updateUserProfile() actions makes a outgoing HTTP request on a server using Axios
When succeeded, I use a mutation to update the user profile in my store and i would like to show a toast from my single file component.
Problem is that the response object is always undefined in my component. Where is my mistake ?
Error :
profile.vue?a62a:328 Uncaught (in promise) TypeError: Cannot read
property 'data' of undefined
at eval (profile.vue?a62a:328)
Store:
/*
* Action used to fetch user data from backend
*/
updateUserProfile ({commit, state}, userData) {
// Inform VueX that we are currently loading something. Loading spinner will be displayed.
commit('SET_IS_LOADING', true);
axiosBackend.put('/user/profile', userData, { headers: { Authorization: state.authString } } ).then(res => {
console.log('PUT /user/profile', res);
// Set user Data in VueX Auth store
commit('SET_USER_DATA', {
user: res.data.data
});
// Reset is Loading
commit('SET_IS_LOADING', false);
return res.data;
})
.catch(error => {
// Reset isLoading
commit('SET_IS_LOADING', false);
});
}
Component:
methods: {
// mix the getters into computed with object spread operator
...mapActions([
'updateUserProfile'
]),
// Function called when user click on the "Save changes" btn
onSubmit () {
console.log('Component(Profile)::onSaveChanges() - called');
const userData = {
firstName: this.firstname,
}
this.updateUserProfile(userData).then( (response) => {
console.log('COMPONENT', response);
if (response.data.status === 200) {
toastr.success("Your profile has been successfully updated.");
}
});
}
}

Well,
It would be better idea if You trigger the toast from the Vuex store itself as mentioned below.
callAddToCart: ({ commit }, payload) => {
axiosBackend.put('/user/profile', userData, { headers: { Authorization:
state.authString }}).then(response => {
commit("setLoading", false, { root: true });
payload.cartKey = response.key;
commit("setNotification", {
type: 'success',
title: `title`,
});
commit("ADD_TO_CART", payload);
});
},
and inside mutation you can have a general notification toast and you can pass type, message and title as below.
setNotification(state, {type, message, title}) {
state.flash = {
type,
title,
message
}
}
NOTE: Do not forget to load toast element at the root level in order to display in the UI.
Here is working example
Hope this helps!

Related

my error object is undefined when i`m using rtk query with try/catch

first of all i want to apologize for my title. I just dont know how to describe my problem.
I am trying to get a bad response from my server and when I try to display that my object is undefined
I have a base query methods here:
export const accountSlice = apiSlice.injectEndpoints({
endpoints: builder => ({
login: builder.mutation({
query: credentials => ({
url: 'account/login',
method: 'POST',
body: { ...credentials },
})
}),
register: builder.mutation({
query: credentials => ({
url: 'account/register',
method: 'POST',
body: { ...credentials },
})
})
})
})
My handle submit on register page ->
const [register, { isLoading, isError }] = useRegisterMutation();
const handleSubmit = async (e) => {
e.preventDefault();
try {
const result = await register({ name, nickName, email, password }).unwrap();
setRegisterResponse(result);
} catch (error) {
setRegisterResponse(error);
}
}
And my logic to show it. When i use console.log(registerResponse) it returnes two logs in console - first object is empty, second object with properties ->
{
isError &&
<h2>
Ooops.. something went wrong:
{
console.log(registerRespnse)
}
</h2>
}
Error in google console
You shouldn't need to call a setRegisterResponse state setter, because that response will just be available for you:
// see data and error here
const [register, { isLoading, isError, data, error }] = useRegisterMutation();
As why it logs undefined once: first the query finishes with an error (which will rerender the component and already fill error I showed above and set isError) and then the Promise resolves and your custom code sets your response local state, which causes a second rerender (and only on the second render, response is set)

React Native Screen wont re-render after fetching data and setting state using HTTP request

I am using the react-native google API to request calendar data via HTTP request using axios.
After the user clicks a login button the function calendarData is initiated and successfully pulls the data and I use setCalendarEvents to set the state of my page to this response data.
I expected this to re-render the screen and display the data but it is not...How can I initiate a page refresh after this data is received from the HTTP request without a manual re-render?
STATE
const [calendarEvents, setCalendarEvents] = useState([]);
calendarData function RUNS AFTER LOG IN BUTTON IS PRESSED BY USER
const calendarData = async function signInWithGoogleAsync() {
try {
const result = await Google.logInAsync({
androidClientId: `['CLIENT ID]`,
iosClientId: `['CLIENT ID']`,
scopes: [
"profile",
"email",
"https://www.googleapis.com/auth/calendar",
"https://www.googleapis.com/auth/calendar.events",
],
});
if (result.type === "success") {
axios({
//HTTP GET REQUEST FOR DATA
method: "get",
baseURL: "https://www.googleapis.com/calendar/v3/calendars/['USER CALENDAR]/events?key=['API KEY']",
headers: {
Authorization: "Bearer " + result.accessToken,
Accept: "application/json",
},
})
.then((response) => {
const responseDataArray = [];
//RESPONSE DATA
response.data["items"].map((event) => {
if (typeof event["start"].dateTime !== undefined) {
responseDataArray.push(event);
}
//SET STATE TO RETREIVED AND FILTERED DATA STORED IN responseDataArray
setCalendarEvents(responseDataArray);
});
})
//CATCH ERRORS
.catch((error) => console.log(error));
} else {
return { cancelled: true };
}
} catch (e) {
return { error: true };
}
};
WHERE DATA SHOULD BE RENDERED ON THE SCREEN AFTER SUCCESSFUL GET
return (
<View>
{calendarEvents.map((event) => {
<View>{event}</View>
}
}
</View>
)
EXAMPLE OF RESPONSE DATA ITEM
I am looking to filter out "start":{"dateTime":"2021-04-16T17:30:00-04:00"} if it exists
{"kind":"calendar#event","etag":"\"3237003518996000\"","id":"7t1q67ai1p7t586peevd7s9mhg","status":"confirmed","htmlLink":"https://www.google.com/calendar/event?eid=N3QxcTY3YWkxcDd0NTg2cGVldmQ3czltaGcgbWF0dEBoZWFydGhkaXNwbGF5LmNvbQ","created":"2021-04-14T16:45:34.000Z","updated":"2021-04-15T15:49:19.498Z","summary":"customer journey beta buddies","creator":{"email":"meilin#hearthdisplay.com"},"organizer":{"email":"meilin#hearthdisplay.com"},"start":{"dateTime":"2021-04-16T17:30:00-04:00"},"end":{"dateTime":"2021-04-16T18:30:00-04:00"},"iCalUID":"7t1q67ai1p7t586peevd7s9mhg#google.com","sequence":0,"attendees":[{"email":"meilin#hearthdisplay.com","organizer":true,"responseStatus":"accepted"},{"email":"matt#hearthdisplay.com","self":true,"optional":true,"responseStatus":"accepted"},{"email":"susie#hearthdisplay.com","responseStatus":"accepted"},{"email":"nathalie#hearthdisplay.com","responseStatus":"accepted"}],"hangoutLink":"https://meet.google.com/xyb-qhpb-uej","conferenceData":{"entryPoints":[{"entryPointType":"video","uri":"https://meet.google.com/xyb-qhpb-uej","label":"meet.google.com/xyb-qhpb-uej"},{"entryPointType":"more","uri":"https://tel.meet/xyb-qhpb-uej?pin=3822838393771","pin":"3822838393771"},{"regionCode":"US","entryPointType":"phone","uri":"tel:+1-818-514-5197","label":"+1 818-514-5197","pin":"222000933"}],"conferenceSolution":{"key":{"type":"hangoutsMeet"},"name":"Google Meet","iconUri":"https://fonts.gstatic.com/s/i/productlogos/meet_2020q4/v6/web-512dp/logo_meet_2020q4_color_2x_web_512dp.png"},"conferenceId":"xyb-qhpb-uej","signature":"AGirE/Jmi4pFHkq0kcqgRyOuAR2r"},"reminders":{"useDefault":true},"eventType":"default"}
Maybe try with this, add conditional rendering :
return ({
calendarEvents.length>0 &&
calendarEvents?.map(...your code)
})

How can I handle a vuex dispatch response?

I'm raising the white flag and asking for suggestions even though I feel the answer is probably right in front of my face.
I have a login form that I am submitting to an api (AWS) and acting on the result. The issue I am having is once the handleSubmit method is called, I am immediately getting into the console.log statement... which to no surprise returns dispatch result: undefined
I realize this is likely not a direct function of vue.js, but how I have the javascript set executing.
Here is my login component:
// SignInForm.vue
handleSubmit() {
try {
const {username, password} = this.form;
this.$store.dispatch('user/authenticate', this.form).then(res => {
console.log('dispatch result: ', res);
});
} catch (error) {
console.log("Error: SignInForm.handleSubmit", error);
}
},
...
Here is what my store is doing. I'm sending it to a UserService I've created. Everything is working great. I am getting the correct response(s) and can log everything out I need. The UserService is making an axios request (AWS Amplify) and returning the response.
// user.js (vuex store)
authenticate({state, commit, dispatch}, credentials) {
dispatch('toggleLoadingStatus', true);
UserService.authenticate(credentials)
.then(response => {
dispatch('toggleLoadingStatus', false);
if (response.code) {
dispatch("setAuthErrors", response.message);
dispatch('toggleAuthenticated', false);
dispatch('setUser', undefined);
// send error message back to login component
} else {
dispatch('toggleAuthenticated', true);
dispatch('setUser', response);
AmplifyEventBus.$emit("authState", "authenticated");
// Need to move this back to the component somehow
// this.$router.push({
// name: 'dashboard',
// });
}
return response;
});
},
...
Where I'm getting stuck at is, if I have error(s) I can set the errors in the state, but I'm not sure how to access them in the other component. I've tried setting the data property to a computed method that looks at the store, but I get errors.
I'm also struggling to use vue-router if I'm successfully authenticated. From what I've read I really don't want to be doing that in the state anyway -- so that means I need to return the success response back to the SignInForm component so I can use vue-router to redirect the user to the dashboard.
Yep. Just took me ~6 hours, posting to SO and then re-evaluating everything (again) to figure it out. It was in fact, somewhat of a silly mistake. But to help anyone else here's what I was doing wrong...
// SignInForm.vue
async handleSubmit() {
try {
await this.$store.dispatch("user/authenticate", this.form)
.then(response => {
console.log('SignInForm.handleSubmit response: ', response); // works
if (response.code) {
this.errors.auth.username = this.$store.getters['user/errors'];
} else {
this.$router.push({
name: 'dashboard',
});
}
}).catch(error => {
console.log('big problems: ', error);
});
} catch (error) {
console.log("Error: SignInForm.handleSubmit", error);
}
},
...
Here's my first mistake: I was calling from an async method to another method - but not telling that method to be async so the call(er) method response was executing right away. Here's the updated vuex store:
// user.js (vuex store)
async authenticate({state, commit, dispatch}, credentials) { // now async
dispatch('toggleLoadingStatus', true);
return await UserService.authenticate(credentials)
.then(response => {
console.log('UserService.authenticate response: ', response); // CognitoUser or code
dispatch('toggleLoadingStatus', false);
if (response.code) {
dispatch("setAuthErrors", response.message);
dispatch('toggleAuthenticated', false);
dispatch('setUser', undefined);
} else {
dispatch('toggleAuthenticated', true);
dispatch('setUser', response);
AmplifyEventBus.$emit("authState", "authenticated");
}
return response;
});
},
...
My second error was that I wasn't returning the result of the method at all from the vuex store.
Old way:
UserService.authenticate(credentials)
Better way:
return await UserService.authenticate(credentials)
Hope this saves someone a few hours. ¯_(ツ)_/¯
This works for Vue3:
export default {
name: 'Login',
methods: {
loginUser: function () {
authenticationStore.dispatch("loginUser", {
email: 'peter#example.com',
})
.then(response => {
if (response.status === 200) {
console.log('Do something')
}
});
},
},
}
In the store you can simply pass back the http response which is a promise.
const authenticationStore = createStore({
actions: {
loginUser({commit}, {email}) {
const data = {
email: email
};
return axios.post(`/authentication/login/`, data)
.then(response => {
toastr.success('Success')
return response
})
},
}
})

Shared method of store fields

Motivation
I store user credentials in redux store. They are filled when user logs in. I would like to have reusable method to fetch data with user's username and password.
State / auth
const initState = {
isLoading: false,
user: undefined,
auth_err: false
};
My attempt
const fetchData = props => async (url, method, body) => {
try {
const response = await fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + Base64.btoa(props.user.username + ":" + props.user.password)
},
body: body
});
console.log(response);
return response;
} catch (err) {
console.log(err);
}
};
const mapStateToProps = state => {
return {
user: state.auth.user
}
};
export const SENDREQUEST = connect(mapStateToProps)(fetchData);
Call
const response = await SENDREQUEST("http://localhost:8080/users", "GET");
But once I call it I get:
TypeError: Cannot call a class as a function
Is there any way at all to create such one?
Any help would be appreciated ♥
I am assuming that you know about redux and its middleware.
First of all the error comes from passing fetchData to the return value of connect : connect returns a function which is a HOC : takes a component, returns a component which is a class here that cannot be called as a function as you do.
A solution for your problem is to use mapDispatchToProps and a middleware, roughly as follow :
class LoginFormPresenter {
render () {
// render your login
return <form onSubmit={this.props.logUser}></form>
}
}
// This makes the LoginFormPresenter always receive a props `logUser`
const LoginFormConnector = connect((state => { user: state.user }), {
logUser: (e) => (
// create a credentials object by reading the form
const credentials = ...;
// return a valid redux action
return {
type: "LOGIN",
credentials
};
)
});
const LoginForm = LoginFormConnector(LoginFormPresenter);
// Create an ad hoc middleware
//
const middleware = ({ dispatch, getState }) => next => action => {
if (action.type === "LOGIN") {
// log your user
fetch()
.then(user => next({ type: "LOGIN", user }));
return next({ type: "PROCESSING_LOGIN" }); // indicate to redux that you are processing the request
}
// let all others actions pass through
return next(action);
};
So the mechanism works like this:
The LoginFormConnector will inject a props logUser into any component it is applied to. This props is a function wich dispatches an action with the credentials of your user. It will also inject a user props taken from the redux state for you to show your user.
Inside a redux middleware you catch the action and use your generic fetchData to process the request. When the request is resolved you dispatch the action to the reducer and update it. No data fetching occurs in the component itself, everything is handled at the middleware level.

ReactJS - Props not available immediately inside componentDidMount on a refresh

Having a frustrating issue, hope someone can help. for full repo available at https://github.com/georgecook92/Stir/blob/master/src/components/posts/viewPosts.jsx.
Get straight into the code -
componentDidMount() {
const {user_id,token} = this.props.auth;
this.props.startLoading();
console.log('props auth', this.props.auth);
if (user_id) {
console.log('user_id didMount', user_id);
this.props.getUserPosts(user_id, token);
}
}
If the component is loaded through the ui from another component it functions as expected. However, if the page is refreshed user_id etc is not available immediately to componentDidMount.
I have checked and it is available later on, but I found that if I move my AJAX call to get the posts to a render method or other lifecycle method like componentWillReceiveProps - the props are being updated constantly and it locks the UI - not ideal.
I am also unsure as to why multiple ajax calls per second are being made if I move the ajax call to the render method.
I hope you can help! Thank you.
Edit.
export function getUserPosts(user_id, token){
return function(dispatch) {
if (window.indexedDB) {
var db = new Dexie('Stir');
db.version(1).stores({
posts: '_id, title, user_id, text, offline',
users: 'user_id, email, firstName, lastName, token'
});
// Open the database
db.open().catch(function(error) {
alert('Uh oh : ' + error);
});
db.posts.toArray().then( (posts) => {
console.log('posts:', posts);
if (posts.length > 0) {
dispatch( {type: GET_POSTS, payload: posts} );
}
});
}
axios.get(`${ROOT_URL}/getPosts?user_id=${user_id}`, {
headers: {
authorisation: localStorage.getItem('token')
}
}).then( (response) => {
console.log('response from getPosts action ', response);
dispatch( {type: GET_POSTS, payload: response.data} );
dispatch(endLoading());
response.data.forEach( (post) => {
if (post.offline) {
if (window.indexedDB) {
db.posts.get(post._id).then( (result) => {
if (result) {
//console.log('Post is already in db', post.title);
} else {
//console.log('Post not in db', post.title);
//useful if a posts offline status has changed
db.posts.add({
_id: post._id,
title: post.title,
user_id: post.user_id,
text: post.text,
offline: post.offline
});
}
} )
}
}
} );
})
.catch( (err) => {
console.log('error from get posts action', err);
if (err.response.status === 503) {
dispatch(endLoading());
dispatch(authError('No internet connection, but you can view your offline posts! '));
} else {
dispatch(endLoading());
dispatch(authError(err.response.data.error));
}
});
}
}
I would suggest using componentWillReceiveProps and saving your props as state to trigger a re-render if a change occurs (ex. a prop changes from undefined to having a value). Something like this will work:
import React, {Component} from 'react';
// using lodash for checking equality of objects
import _isEqual from 'lodash/isEqual';
class MyComponent extends Component {
constructor(props={}) {
super();
this.state = props;
}
// when the component receives new props, like from an ajax request,
// check to see if its different from what we have. If it is then
// set state and re-render
componentWillReceiveProps(nextProps) {
if(!_isEqual(nextProps, this.state)){
this.setState(nextProps);
}
}
render() {
return (
<div>{this.state.ItemPassedAsProp}</div>
);
}
}

Categories

Resources