Why ReactJS state cannot be accessed in componentDidMount - javascript

I am pulling the data from MongoDB database using axios and set the value to a state value named invoices
I do this in componentDidMount. And again I want to access that state (i.e. invoices) within the componentDidMount. I am trying to set invoices value to another state value called dataa. But always ends up getting empty/null.
Problem is the state has Not been set, value is empty
This is what my code snippet looks like:
componentDidMount() {
axios
.get("http://localhost:4005/purchaseinvoices")
.then(response => {
this.setState({
invoices: response.data //setting value to invoices
});
})
.then(
this.setState({
dataa: this.state.invoices //setting value to dataa
})
)
.catch(err => {
console.log(err);
});
//but this gives 0
alert(this.state.invoices.length)
}
what is the possible cause of problem and how can I fix this?

Update the data directly from response instead of using this.state.invoices.since this.setState is an async call,takes some ms delay to update the state.
componentDidMount() {
axios
.get("http://localhost:4005/purchaseinvoices")
.then(response => {
this.setState({
invoices: response.data //setting value to invoices
data: response.data
});
})
.catch(err => {
console.log(err);
});
}
If you want to see the data on first load of page ,try server side rendering

That's because axios and setState are asynchronous.
But, you can see updated stated in componentDidUpdate or in render function.
Edit: Also you can access just the stated is updated like below;
axios
.get("http://localhost:4005/purchaseinvoices")
.then(response => {
this.setState({
invoices: response.data, //setting value to invoices
dataa: response.data
}, () => {
alert(this.state.invoices.length)
});
})
.catch(err => {
console.log(err);
});
}

Related

Setting API data as state in React is undefined, but the API data exists

I have a weird issue with my React state. I'm fetching data from my NodeJS backend, and it comes back correctly in my frontend React app. But when I try to initialize a state with the data that was fetched, the state's value is "undefined", even though I know the data is coming back from the backend correctly.
here are the important parts of my react code:
const [currentCityData, setCurrentCityData] = useState({});
const fetchData = (cityValue) => {
axios.get('http://localhost:5000/get-weather', {
params: {
cityValue: cityValue
}
})
.then(res => console.log(res?.data?.data[0]))
.then((res) => setCurrentCityData(res?.data?.data[0]))
.then(console.log(currentCityData))
.catch(error => console.log(error))
};
useEffect(() => {
fetchData('toronto&country=canada');
}, []);
I'm getting the weather of a city. When I do a console log of the data I get back .then(res => console.log(res?.data?.data[0])) inside fetchData, the data is correct (its just an object with many weather properties). The line after that I set my state currentCityData, but then when I console log my currentCityData right after, its undefined.
Exactly what am I doing wrong here?
You are not returning aything from the first promise then handler. :
axios.get('http://localhost:5000/get-weather', {
params: {
cityValue: cityValue
}
})
.then(res => console.log(res?.data?.data[0])) // <--- here you should return
.then((res) => setCurrentCityData(res?.data?.data[0]))
.then(console.log(currentCityData))
.catch(error => console.log(error))
};
change your code to :
axios.get('http://localhost:5000/get-weather', {
params: {
cityValue: cityValue
}
})
.then(res => {console.log(res?.data?.data[0]); return res?.data?.data[0] }) // <--- here you should return
.then((res) => setCurrentCityData(res?.data?.data[0]))
.then(console.log(currentCityData))
.catch(error => console.log(error))
};
Demo : Demo

State of array is not updated in ComponentDidMount

I have a weird situation, where I have an array as a state:
this.state = { ActivityItem: []} and I am pushing values to it from library that calls an API like this:
getDataFromKit(dateFrom) {
Kit.getSamples(stepCountSample, (err, results) => { //api call
if (err) {
return;
}
const newData = results.map(item => { return { ...item, name: 'itemAmount' } });
this.setState({ d: [...this.state.ActivityItem, ...newData] })
})
Then, I call this method from ComponentDidMount() for array to be loaded
componentDidMount() {
this.getDataFromHealthKit(ONEDAYINTERVAL);
console.log("values are", this.state.ActivityItem)
}
Now, the weirdest part: somehow the array is empty in ComponentDidMount, but when I display elements of Array in return of render() function it displays all the values that were added correctly. How is that possible and how I might fix that?
setState is asynchronous in nature. Therefore, logging the state just immediately after setting it can give this behaviour but if set properly, it will display the required content which is happening in your case. Also componentDidMount is called only once in the beginning so you can check for logs in componentDidUpdate method.
State updates are async in nature. If you want to print the state soon after setting your state in class component, then pass a function to the 2nd argument of setState.
Like this
componentDidMount() {
this.getDataFromHealthKit(ONEDAYINTERVAL);
// remove console.log
}
...
getDataFromKit(dateFrom) {
...
this.setState({ ActivityItem: [...this.state.ActivityItem, ...newData] }), () => {
console.log("values are", this.state.ActivityItem) //<----
}
})
...
}
use prevstate while updating the state value. React setState is an asynchronous update and does batch updating. Using prevState makes sure that the state value is updated before calculating new state value.
getDataFromKit(dateFrom) {
let stepCountSample = {
startDate: dateFrom.toISOString(),
type: "Type1"
};
Kit.getSamples(stepCountSample, (err, results) => {
//api call
if (err) {
return;
}
const newData = results.map(item => {
return { ...item, name: "itemAmount" };
});
this.setState(prevState => {
ActivityItem: [...prevState.ActivityItem, ...newData];
});
});
}
DOCUMENTATION would help understand the concept
Also, console.log would directly not give the updated state, since state updates are batched. You can use a callback method to setState function. The callback will run only after successfull updation of state value

fetching json in seperate component

I've made an application and want to add more components which will use the same json I fetched in "personlist.js", so I don't want to use fetch() in each one, I want to make a separate component that only does fetch, and call it in the other components followed by the mapping function in each of the components, how can make the fetch only component ?
here is my fetch method:
componentDidMount() {
fetch("data.json")
.then(res => res.json())
.then(
result => {
this.setState({
isLoaded: true,
items: result.results
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
error => {
this.setState({
isLoaded: true,
error
});
}
);
}
and here is a sandbox snippet
https://codesandbox.io/s/1437lxk433?fontsize=14&moduleview=1
I'm not seeing why this would need to be a component, vs. just a function that the other components use.
But if you want it to be a component that other components use, have them pass it the mapping function to use as a prop, and then use that in componentDidMount when you get the items back, and render the mapped items in render.
In a comment you've clarified:
I am trying to fetch the json once, & I'm not sure whats the best way to do it.
In that case, I wouldn't use a component. I'd put the call in a module and have the module expose the promise:
export default const dataPromise = fetch("data.json")
.then(res => {
if (!res.ok) {
throw new Error("HTTP status " + res.status);
}
return res.json();
});
Code using the promise would do so like this:
import dataPromise from "./the-module.js";
// ...
componentDidMount() {
dataPromise.then(
data => {
// ...use the data...
},
error => {
// ...set error state...
}
);
}
The data is fetched once, on module load, and then each component can use it. It's important that the modules treat the data as read-only. (You might want to have the module export a function that makes a defensive copy.)
Not sure if this is the answer you're looking for.
fetchDataFunc.js
export default () => fetch("data.json").then(res => res.json())
Component.js
import fetchDataFunc from './fetchDataFunc.'
class Component {
state = {
// Whatever that state is
}
componentDidMount() {
fetchFunc()
.then(res => setState({
// whatever state you want to set
})
.catch(err => // handle error)
}
}
Component2.js
import fetchDataFunc from './fetchDataFunc.'
class Component2 {
state = {
// Whatever that state is
}
componentDidMount() {
fetchFunc()
.then(res => setState({
// whatever state you want to set
})
.catch(err => // handle error)
}
}
You could also have a HOC that does fetches the data once and share it across different components.

setState not working inside AsyncStorage in react native?

setState not working inside AsyncStorage in React Native.
constructor(props) {
super(props);
this.state = {userId: ''};
}
componentDidMount() {
AsyncStorage.getItem('USER_ID', (err, result) => {
if (!err && result != null) {
this.setState({
userId: result
});
}
else {
this.setState({
userId: null
});
}
});
alert(this.state.userId);
let userId = this.state.userId;
fetch('http://localhost/JsonApi/myprofile.php', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId: userId,
}),
})
.then((response) => response.json())
.then((responseJson) => {
this.setState({userDetails: responseJson});
})
.catch((error) => {
console.error(error);
});
}
Setting the userId value using setState and alert returns no value at all. Tried other solutions from Stackoverflow but not as per my expectation.
Note: Code updated. After getting userId from AsyncStorage, it will be passed to fetch. Here, userId value is missing.
2 ways to do this. One is Simple but other is correct way according to react recommendation
One is here- pass value to state directly.
.then((responseJson) => {
// this.setState({userDetails: responseJson});
this.state.userDetails=responseJson;
this.setState({}); //for update render
})
Second Way is here
in the render function Check state Value like this .if UserDetails state is null it will be not give you error whenever userDetails state get data render execute again and provide perfect result.
render() {
return (
<div>
{this.state.userDetails ?
this.state.userDetails.map((data, index) =>
<tr key={index}>
<td>{data.userName}</td>
<td>{data.userEmail}</td>
</tr>
)
: null
}
</div>)}
Let me know gain. if facing issue
Try to alert after updating state. You will get callback once state is updated.
this.setState({
userId: result
},function(){
console.log("userId in async = ", this.state.userId);
alert(this.state.userId);
});
I don't know why you wrote so much code.
First way
AsyncStorage.getItem("USER_ID").then((value) => {
console.log("userId in async = " + value);
this.setState({
userId: value
});
});
You don't need to check error & result both because if that is null, you are setting userId null in state. so you can directly set value to state userId.
Also set a log to see what is output of your async storage userId.
Please also verify that you are setting value in "USER_ID" somewhere.
Second way
There can different ways also like using async method.
const getUserId = async () => {
try {
const userId = await AsyncStorage.getItem('USER_ID') || 'none';
} catch (error) {
// Error retrieving data
console.log(error.message);
}
return userId;
}
and you can use
this.setState ({
userId : getUserId()
});
I don't like this way because I need to create another method with async & await keywords.
I use the first way so.
Update
Do your work related to userId inside getIten();, because you alert userId immediately after calling AsyncStorage. And AsyncStorage returns value after you call alert.
AsyncStorage.getItem("USER_ID").then((value) => {
console.log("userId in async = " + value);
this.setState({
userId: value
});
alert(this.state.userId); // move this line here
});
// removed from here

How to make to make sequential async calls and pass data to a sibling component properly?

I'm trying to build a hackernews clone as a practice to reactjs. Here I'm trying to build this only with react and later I'm going to build this with Redux.
This is my component structure.
--main
|--menubar
|--articles
Here is the codepen for the project.
I'm having two issues here.
1.)
Here I'm passing data through state and props.I'm calling the API in componentDidMount method on menubar component and pass it to the articles component through main component. But it doesn't render the list when it receives the data through props in componentWillReceiveProps method. To render it I have to click News (which has no connection with fetching data, it's just printing a log) which will recall the API method. How can I render the data in this.state.articleList when the data is received through props and set the data.
2.)
In the API call, I have defined to get first 20 posts only. But when I click news, I'm getting random number of (<20) posts rendered in each time. Why is that ? As the API call is same, shouldn't it render same amount of(20) posts ? Why it differ ?
Is the both issues because of Asynchronous methods ? If so how can I solve them ?
Actually its because of async, i edited it using the async library edited the fetchdata() and added getItems().
The advantage of using map is that it will return an array of results itself so we need not maintain an array.
var async = require("async");
fetchdata() {
fetch("https://hacker-news.firebaseio.com/v0/topstories.json")
.then(res => res.json())
.then(data => {
this.setState({ storyList: data }, () => {
this.getItems(this.state.storyList);
});
})
.catch(err => console.log(`Error fetching data : ${err}`));
}
getItems(storyList) {
async.mapLimit(storyList, 10,
function(item, callback) {
console.log(item);
fetch(`https://hacker-news.firebaseio.com/v0/item/${item}.json`)
.then(res => res.json())
.then(data => {
//pass the data here
callback(null, data);
});
},
function(err, dataArray) {
if (err) {
console.error(err.message);
} else {
//dataArray will contain an array of results from map
this.props.getData(dataArray);
}
}
);
}
Hi after getting the data inside getItems binding the data to callback getData as follows
getItems(storyList) {
var story_list = [];
async.mapLimit(
storyList,
10,
((item, callback) =>{
console.log(item);
fetch(`https://hacker-news.firebaseio.com/v0/item/${item}.json`)
.then(res => res.json())
.then(data => {
story_list.push(data);
this.props.getData(story_list);
});
}),
((err) =>{
if (err) {
console.error(err.message);
} else {
this.props.getData(story_list);
}
})
);
}

Categories