I am teaching myself how to "fetch" data within React. However, although I am able to grab the JSON data from a local file, it somehow disappears when I try to place it into the component's state.
I am using the following tutorial. The precise code I'm copying is the second image at this link:
https://www.robinwieruch.de/react-fetching-data/#react-how-fetch-data
For example, the below code, in the log, returns the correct data:
constructor(props) {
super(props);
this.state = { cardData: [] };
}
componentDidMount() {
fetch(cardDataJsonLocation)
.then(response => response.json())
.then(data => console.log(data.cardData));
}
When I look at the console in my browser, the correct data is being logged - an array with 2 objects in it:
[{…}, {…}] 0: {name: "dom", id: 1} 1: {name: "dave", id: 2}
length: 2 __proto__: Array(0)
However, when I change the above code to actually place the above array data in my component's state:
componentDidMount() {
fetch(cardDataJsonLocation)
.then(response => response.json())
.then(data => this.setState({ cardData: data.cardData }))
.then(console.log(this.state));
}
When I log the state, there's no change from the original state I set in the constructor.
In this case, it logs an empty array. If I set the state (in the constructor) to, say, [1,2,3] or null, then that value comes down instead.
Am I missing a step in the fetch process? It's as if it skips the step where I try to setState after fetch. Thanks.
As others have pointed and as described in the documentation, setState() is an async method.
If you need to access the state immediately after calling setState, then put your code in a callback and pass this callback as the second parameter of setState, like in:
this.setState({ cardData: data.cardData }, () => { console.log(this.state); })
Related
I'm reading data from firestore and stores it in state array of objects.
when i
console.log(this.state.array)
it returns the whole array with all the data of the objects, but when i
console.log(this.state.array.name)
or
console.log(this.state.array[0])
it returns undefined
.
I have tried to get the data with
forEach
loop but it seems to be not working as well.
constructor(props) {
super(props);
this.state = { tips: [] };
}
componentDidMount() {
firebase.firestore().collection('pendingtips').get()
.then(doc => {
doc.forEach(tip => {
this.setState([...tips], tip.data());
console.log(this.state.tips);
});
})
.catch(() => Alert.alert('error'));
}
renderTips() {
console.log(this.state.tips); //returns the whole array as expected
console.log(this.state.tips[0].name); //returns undefined
return this.state.tips.map(tip => <PendingTip key={tip.tip} name={tip.name} tip={tip.tip} />); //return null because tip is undefined
}
render() {
return (
<View style={styles.containerStyle}>
<ScrollView style={styles.tipsContainerStyle}>
{this.renderTips()}
</ScrollView>
</View>
);
}
the array structure is:
"tips": [
{ name: "X", tip: "Y" },
{ name: "Z", tip: "T" }
]
so I expect this.state.tips[0].name will be "X" instead of undefined.
thanks in advance.
First of all you should fetch data in componentDidMount instead of componentWillMount.
https://reactjs.org/docs/faq-ajax.html#where-in-the-component-lifecycle-should-i-make-an-ajax-call
Secondly, you should use this.setState to update your state, instead of mutating it directly.
componentDidMount() {
firebase
.firestore()
.collection("pendingtips")
.get()
.then(docs => {
const tips = docs.map(doc => doc.data());
this.setState({ tips });
})
.catch(() => Alert.alert("error"));
}
I Found out that the problem was that JavaScript saves arrays as objects.
for example this array:
[ 'a' , 'b' , 'c' ]
is equal to:
{
0: 'a',
1: 'b',
2: 'c',
length: 3
}
"You get undefined when you try to access the array value at index 0, but it’s not that the value undefined is stored at index 0, it’s that the default behavior in JavaScript is to return undefined if you try to access the value of an object for a key that does not exist."
as written in this article
firesore requests are async, so by time your request gets execute your component is getting mounted and in a result you are getting undefined for your state in console.
You must do API call in componentDidMount instead of componentWillMount.
Mutating/changing state like this, will not trigger re-render of component and your component will not get latest data,
doc.forEach(tip => {
this.state.tips.push(tip.data());
console.log(this.state.tips);
});
You must use setState to change your state, doing this your component will get re-render and you have latest data all the time.
componentDidMount(){
firebase.firestore().collection('pendingtips').get()
.then(doc => {
const tipsData = doc.map(tip => tip.data());
this.setState({tips:tipsData},() => console.log(this.state.tips));
})
.catch(() => Alert.alert('error'));
}
While calling renderTips function make sure your state array has data,
{this.state.tips.length > 0 && this.renderTips()}
I'm building a simple app in pure Reactjs. Component I'm having problems is a component that is supposed to render a number of buttons by mapping an array that has previously been populated by fetching some data from an external API. This array is populated within a class method and the results are eventually copied onto another array which is part of the state of the component
When I console.log the contents of the array on the render method of my component, everything looks fine. However if I try to print a specific element by its index, "undefined" is printed on the console. As a result the map function does not render all the desired buttons.
I have managed to find different documentation around the way I'm populating the array but none of the articles so far suggest that I'm doing anything fundamentally wrong. At least not that I can see.
State stores an empty array to start with and within the componentWillMount method an API gets called that fetches data and updates the array as per the below:
this.state = {
resources: []
}
getAPIavaiableResources(api_resource) {
let buttonsArray = []
fetch(api_resource)
.then(response => response.json())
.then(data => {
for (let i in data) {
buttonsArray.push({id: i, name: i, url: data[i]})
}
}).catch(error => console.log(error))
this.setState({resources: buttonsArray})
}
componentWillMount() {
this.getAPIavaiableResources(ROOT_RESOURCE)
}
render() {
const { resources } = this.state;
console.log(resources)
console.log(resources[0])
return (
<div className="buttons-wrapper">
{
resources.map(resource => {
return <Button
key={resource.id}
text={resource.name}
onClick={this.handleClick}
/>
})
}
</div>
)
}
This is what gets printed onto the console on the render method.
[]
0: {id: "people", name: "people", url: "https://swapi.co/api/people/"}
1: {id: "planets", name: "planets", url: "https://swapi.co/api/planets/"}
2: {id: "films", name: "films", url: "https://swapi.co/api/films/"}
3: {id: "species", name: "species", url: "https://swapi.co/api/species/"}
4: {id: "vehicles", name: "vehicles", url: "https://swapi.co/api/vehicles/"}
5: {id: "starships", name: "starships", url: "https://swapi.co/api/starships/"}
length: 6
__proto__: Array(0)
Can anyone see what I'm doing wrong? I'm pushing an object because I do want an array of objects albeit arrays in Javascript are objects too. Any help would be appreciated.
Your current implementation is setting state before you have the data, and then mutating state once the api call comes back. React can't tell when you mutate things, and thus doesn't know to rerender. Only when you call setState (or when it receives new props) does it know to rerender.
Instead, wait until you have the data and only then call setState with the populated array.
getAPIavaiableResources(api_resource) {
fetch(api_resource)
.then(response => response.json())
.then(data => {
let buttonsArray = []
for (let i in data) {
buttonsArray.push({id: i, name: i, url: data[i]})
}
this.setState({resources: buttonsArray})
}).catch(error => console.log(error))
}
componentDidMount() {
this.getAPIavaiableResources(ROOT_RESOURCE)
}
The above example also updates the code to use componentDidMount instead of componentWillMount. componentWillMount is deprecated, and wasn't intended for this sort of case anyway.
Currently you are setting the state without waiting for the promise to be resolved. In order to do that, move this.setState({resources: buttonsArray}) after for loop.
In addition, you can render the component conditionally until the you get what you want from the remote resource by doing:
render () {
const { resources } = this.state;
return resources.length
? (
<div>Your content...</div>
)
: null // or some loader
}
I have a program that uses Axios to get data with API calls. I want to store the result as a object in my this.state.matrixDictionary variable. but everytime i make another API call the previous object gets overwritten. I want to create something like this
this.setState({
matrixDictionary: {
[0]: result,
}
})
Then next time i make another api call to get other result i want it to be like this:
this.setState({
matrixDictionary: {
[0]: result,
[1]: result,
}
})
But i dont want to add the [1] manually, i want it to be created depending on how many times i make the API call to store the objects. If i make 5 calls then the object should be now [0],[1],[2],[3],[4] so i can easily keep track of the objects and change their values later.
How is this best achieved?
fetchDataAPI(APIUrl){
this.setState({ isLoading: true });
console.log("Fetching from: " + APIUrl);
return axios.get(APIUrl,{
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
}
})
.then(result => {
this.setState({isLoading: false});
console.log(result.data);
return result.data;
})
.catch(error => {
this.setState({error, isLoading: false })});
}
UPDATE
I used the fix from Roman Batsenko, Next question is how do I then change a property in that object and put it back in setState.
I guess good practice is to use JS Spread syntax for that like ...state.
It depends on the format of answer from your API but I think it would be not so hard to achieve this with:
axios.get(APIUrl,{
/* ... */
})
.then(result => {
this.setState({
isLoading: false,
matrixDictionary: [...this.state.matrixDictionary, result.data]
});
})
make an array of object in your intial state like
this.state = {
matrixDictionary: []
}
and when you call your api push your response object in array so that will store always in another index and finally you make array of objects.
this.setState({ matrixDictionary: result.data});
it may help you.
Why not save the objects in an array, so you can have them in order:
in the constructor:
this.state = {
matrixDictionary: []
}
in your API call:
this.setState(prevState => ({
values: prevState.matrixDictionary.concat(result.data),
}));
You can access them like this:
this.state.matrixDictionary[0] // your first api call
this.state.matrixDictionary[1] // your second api call
this.state.matrixDictionary[2] // your third api call
I have a function that returns json:
const mapDispatchToProps = dispatch => {
return {
articleDetail: (id) => {
return dispatch(articles.articleDetail(id));
}
}
};
I get the result of the call here:
class ArticleDetail extends Component {
constructor(props) {
super(props);
this.state = {
articleId: props.match.params.id,
asd: "",
art:{}
};
}
componentWillMount() {
this.props.articleDetail(this.state.articleId).then((res) => {
console.log(res.article);
this.setState({art:res.article})
});
this.setState({asd: "asda"})
}
console.log(res.article) return me: {id: 1, author: {…}, headline: "First test article", description: "sadasdsads", img_name: "D.png", …}
but I can't write this result in state, just outside the function, as I did with asd.
I would appreciate it if you would help me, maybe there is some way to write the result of this.props.articleDetail () in state.
I also wanted to ask if I could write the result of calling this function into a variable, and the function returns promise
And also, is it possible to set some variable over this function and record what my console.log "returns" to my external variable.
Thank you so much for your time.
how did you check if the state changed?
In order to properly check if the state has been updated apply a callback to the setState function like this (remember that setState is async):
this.setState({ art: res.article }, () => {
// this happens after the state has been updated
console.log(this.state.art);
});
in regards to your comment about setting the state in the lifecycle methid then it's perfectly fine as long as you do it in componentWillMount and not in componentDidMount.
This is how my state looks like:
constructor(props, context) {
super(props, context);
this.state = {
show: false,
btnLabel: 'GO!',
car: {
owner: false,
manufacturer: false,
color: false
}
};
}
and this is how I modify state:
handleClickFetchPrice() {
this.setState({btnLabel: 'Fetching data...' });
console.log(this.state.fetchPriceBtn);
const url = 'some url';
axios.get(url)
.then(res => {
let car = [...this.state.car];
car.owner = res.data.owner;
car.manufacturer = res.data.manufacturer;
car.color = res.data.color;
this.setState({car});
})
}
The attribute car is updated, but fetchPriceBtn is not - the output of console.log(this.state.fetchPriceBtn); is still GO!.
What am I overlooking? Why the fetchPriceBtn is not updated?
React setState is an asynchronous process - you don't know exactly when it will be updated, you can only schedule the update.
To achieve your desired functionality, you can provide a callback into the setState method.
this.setState({ btnLabel: 'Fetching data...' }, () => console.log(this.state.fetchPriceBtn))
You can learn more following the documentation on the method.
#christopher is right, setState is an asynchronous process. But when second time call handleClickFetchPrice() function your btnLabel is value will be equal to Fetching data...
As answered in previous answers setState is asynchronous, so your console.log can't catch up the state change immediately. Again as suggested you can use callback function to track this change but if you use console.log just for debugging or want to see what changes in your state you can do this in your render function. And using a callback just for debug is not a nice way. Its purpose somehow different and if you check the official documentation, componentDidMount method is being suggested for such logic.
render() {
console.log( this.state.foo );
return (...)
}
If you do that you see two console.log output, one before state change and one after.
Also, your state operations might be enhanced. You car property is not an array, but you are converting it to an array and setting it? Is this what you intend:
axios.get(url)
.then(res => {
const { owner, manufacturer, color } = res.data;
this.setState( prevState => ( { car: { ...prevState.car, owner, manufacturer, color } } ) );
})
Here we are not mutating our state directly, instead we are using spread operator and setting the desired properties. For your example we are setting the whole property actually.
One last note, I think you want to do that something like that:
this.setState( { btnLabel: "fetching } );
axios.get(url)
.then(res => {
const { owner, manufacturer, color } = res.data;
this.setState( prevState => ( { car: { ...prevState.car, owner, manufacturer, color }, btnLabel: "go" } ) );
})
If your intention is somehow to do a status change/check this might no be a good logic as you have seen setState is not synchronous. Do this carefully.