Hi i'm trying to fetch a user data from jsonplaceholder and update my state with that data. I had no problem fetching the data and logging it to the console. But when i try to setState, i still get an empty object.
I appreciate any help. Thanks.
This is my code:
class ProfilePage extends React.Component {
state = {
profileDetails: {},
};
componentDidMount() {
this.fetchDetails();
}
fetchDetails = async () => {
const baseUrl = "https://jsonplaceholder.typicode.com";
const pathname = this.props.history.location.pathname;
const response = await fetch(`${baseUrl}${pathname}`);
const data = await response.json();
console.log(data); // I can see the data i want here in the console.
this.setState = { profileDetails: data };
console.log(this.state.profileDetails); // I get an empty object here.
};
render() {
return <h1>Name: {this.state.profileDetails.name}</h1>;
}
}
export default ProfilePage;
Thanks everyone for taking the time to answer. Apparently i used setState wrong and missed the fact that it's asynchronous.
From docs of setState
React does not guarantee that the state changes are applied
immediately.
If you want to use up-to-date data, use callback argument (and use it as function, instead of assignment, because it is a method, not a property)
this.setState({ profileDetails: data }, () => {
console.log(this.state.profileDetails)
})
Change this
this.setState = { profileDetails: data };
console.log(this.state.profileDetails);
into this
this.setState({ profileDetails: data });
Put console.log(this.state.profileDetails); inside render for you to see your new state.
setState is a function that recieves data as parameters.
but you use it like setState is a json object
setState - is a method.
Please change code like this - this.setState({ profileDetails: data });
The right way to set state is this,
this.setState({ profileDetails: data })
You have to set state by this way only.
Give a condition for check the data is available or not:-
if(data)
this.setState = ({ profileDetails: data });
Related
I am trying to set my state to the data I'm getting from my API with a GETTER in the store.
during the mounted() lifecyclehook trigger the GETTER getProducts() which looks like this:
export const getters = {
async getProducts() {
axios.get('/api/products')
.then(res => {
var data = res.data
commit('setProducts', data)
})
.catch(err => console.log(err));
}
}
In the GETTER I try to trigger a MUTATION called setProducts() which looks like this:
export const mutations = {
setProducts(state, data) {
state.products = data
}
}
But when I run this I get the error ReferenceError: commit is not defined in my console.
So obviously what goes wrong is triggering the MUTATION but after looking for 2 days straight on the internet I still couldn't find anything.
I also tried replacing commit('setProducts', data) with:
this.setProducts(data)
setProducts(data)
Which all ended with the error "TypeError: Cannot read properties of undefined (reading 'setProducts')"
If your function getProduct is defined in a Vue component, you have to access the store like this :
this.$store.commit('setProducts', data)
If your function is not defined in a Vue component but in an external javascript file, you must first import your store
import store from './fileWhereIsYourStore.js'
store.commit('setProducts', data)
If your getters export is literally the definition of your store's getters, you can use the solution of importing the store first, but you should know that it is clearly not a good practice to make commits in getters. There must be a better solution to your problem.
EDIT : To answer your comment, here's how you could do it:
// Your store module
export default {
state: {
products: []
},
mutations: {
SET_PRODUCTS(state, data) {
state.products = data
}
},
actions: {
async fetchProducts(store) {
await axios.get('/api/products')
.then(res => {
var data = res.data
store.commit('SET_PRODUCTS', data)
})
.catch(err => console.log(err));
}
}
}
Now, you can fetch products and populate your store in each of your components like this :
// A random Vue Component
<template>
</template>
<script>
export default {
async mounted() {
await this.$store.dispatch('fetchProducts')
// now you can access your products like this
console.log(this.$store.state.products)
}
}
</script>
I didn't tested this code but it should be ok.
Only actions do have commit in their context as you can see here.
Getters don't have commit.
Otherwise, you could also use mapActions (aka import { mapActions } from 'vuex'), rather than this.$store.dispatch (just a matter of style, no real difference at the end).
Refactoring your code to have an action as Julien suggested is a good solution because this is how you should be using Vuex.
Getters are usually used to have some state having a specific structure, like sorted alphabetically or alike. For common state access, use the regular state or the mapState helper.
I have the following method in my Vuex action:
const actions = {
async fetchByQuery({ commit, title }) {
console.log(title);
//other codes
},
};
And method to reach to vuex action:
methods: {
...mapActions(["fetchByQuery"]),
getData(title) {
console.log("teacher");
this.fetchByQuery(title);
}
}
But the console.log() from action is giving undefined output in the console.
What am I missing here ??
You got the parameters inside your action wrong.
({ commit, title }) has to be ({ commit }, title)
Otherwise you would have to call it with an object with a property title.
Vuex actions expect two parameters, the context object { commit } and the payload (title, in your case)
Change your action declaration to this:
const actions = {
async fetchByQuery({ commit }, title) {
console.log(title);
//other codes
},
};
Assume data has already been cached in sessionStorage. I have hydrateStateWithSessionStorage in an external CacheService.js file. I import this file. When I try to pass this.setState to this function, I get this error:
Uncaught TypeError: Cannot read property 'updater' of undefined
How can I solve this? I could possibly use a the React hook useState and pass the setter function, but what if I want to use a class component instead of functional component? Or am I simply unable to pass setState because it implicitly uses the 'this' keyword in its implementation?
hydrateStateWithSessionStorage(state, setState) {
// for all items in state
for (let key in state) {
// if the key exists in localStorage
if (sessionStorage.hasOwnProperty(key)) {
// get the key's value from localStorage
let value = sessionStorage.getItem(key);
// console.log(value)
// parse the localStorage string and setState
try {
value = JSON.parse(value);
console.log('before')
setState({ [key]: value });
console.log('after')
} catch (e) {
// handle empty string
setState({ [key]: value });
}
}
}
}
//in the component consuming CacheService
//this.Cache = new CacheService(); //in the constructor
componentDidMount() {
this.Cache.hydrateStateWithLocalStorage(this.state, this.setState);
this.Auth.fetch('api/upcomingbill/').then((data) => {
this.setState({ list: data })
});
}
I would treat this function more of a check return sessionStorage object if nothing return undefined. Send this.state into the fn() then check response and return the response.
componentDidMount() {
const newState = hydrateState(this.state):
!!newState && this.setState(newState)
}
Just a brain dump..
How about this?
componentDidMount() {
this.Cache.hydrateStateWithLocalStorage(this);
this.Auth.fetch('api/upcomingbill/').then((data) => {
this.setState({ list: data })
});
}
And then using setState like so...
hydrateStateWithSessionStorage(ReactComp) {
for (let key in ReactComp.state) {
...
ReactComp.setState({ [key]: value });
....
}
}
FYI, this is not a React specific issue. The reason you are getting that error is, you are passing just a method that internally uses "this" (of that particular class) to the hydrateStateWithSessionStorage function. By the time, that method is called in the function, the scope has changed and "this" is not defined or not the same anymore.
I have a React component like this:
class Example extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
address: '',
phone: ''
}
}
componentDidMount() {
//APIcall1 to get name and set the state
//i.e., axios.get().then(this.setState())
//APIcall2 to get address and set the state
//APIcall3 to get phone and set the state
}
}`
As you can see I am making three API get requests to get the details and setting the state three times after getting the data. Due to this, I am getting this error:
Warning: Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
By the way, I am not causing a state change in the render method. Anyway to solve this issue?
As axios.get returns a promise, you can link them together before calling setState. For example using Promise.all:
componentDidMount() {
Promise.all([
APIcall1, // i.e. axios.get(something)
APIcall2,
APIcall3
]).then(([result1, result2, result3]) => {
// call setState here
})
}
Just beware that if any of the api calls fail, Promise.all will catch, and setState won't be called.
In axios you have the method axios.all:
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
// Both requests are now complete
}));
Or you can use the standard Promise.all:
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
Promise.all([getUserAccount(), getUserPermissions()])
.then(data => {
// Both requests are now complete
});
I'm managing my state using Mobx. I call an action for an http request to load pictures, then update the pictures property, then update the loading property. When I load the component that makes the call and console.log the store properties, the loading property is updated, but the picture property is still undefined. It's not until the second render of the component that the picture property is defined Here's an example:
export class PhotoStore {
#observable picInfo = []
#observable loading = true
#action loadPics() {
this.loading = true;
let dataURL = 'some url';
return axios.get(dataURL)
.then(res => {this.picInfo = res.data})
.then(this.loading = false)
}
class PhotoGallery extends React.Component{
componentWillMount(){
this.PhotoStore.loadPics();
}
render(){
console.log(//these two properties)
//returns false and undefined object
return(
//some code
);
}
}
I know I can just check for the picInfo.length before rendering the JSX, but I want to make this work. Thanks for any tips in advance!
You don't need the second .then clause. When you set this.picInfo, also set this.loading. Because you put the loading state change in a chained promise, there is a timing issue where the #observable tries to evaluate before the loading is set.
https://mobx.js.org/best/actions.html - see runInAction and the asyncAction decorator
#action loadPics() {
this.loading = true;
let dataURL = 'some url';
return axios.get(dataURL)
.then(res => {runInAction(() => {this.picInfo = res.data})}
.then(runInAction(() =>this.loading = false))
}