I feel like this is a dumb question but I can't find the answer.
Currently I have state:
this.state = {
jsonReturnedValue: []
}
I do a fetch request and get an array of data:
componentDidMount() {
fetch('http://127.0.0.1:8000/api/printing/postcards-printing')
.then(response => response.json())
.then(json => {
this.setState({ jsonReturnedValue: [...this.state.jsonReturnedValue, json.printCategory.products] }, () => console.log(this.state));
});
}
This pushes the array pulled from my fetch request but it creates this:
jsonReturnedValue
[0]Array
[3] Array <--- the array I'm wanting is nested in the original array.
What I need is
jsonReturnedValue
[3]Array
I need my fetch response not to be nested in the already made array.
I see it being one of two issues.
Option 1: [...this.state.jsonReturnedValue, ...json.printCategory.products] Note the spread operator on the second index. I think it's this one!
Option 2: We should see the response body structure but it may be that you need to select a lower-level property on the response. For example, json.data.printCategory.products instead of json.printCategory.products.
You're so close:
this.setState({ jsonReturnedValue: [...this.state.jsonReturnedValue, ...json.printCategory.products] }
You want to concatenate those arrays, but what you were doing (with out the second spread operator) was just adding an array as the item item of another.
Spread both of the arrays you want to concatenate:
this.setState({
jsonReturnedValue: [
...this.state.jsonReturnedValue,
...json.printCategory.products,
],
}, () => console.log(this.state));
Or use Array#concat:
this.setState({
jsonReturnedValue: this.state.jsonReturnedValue
.concat(json.printCategory.products),
}, () => console.log(this.state));
Or if you wanted to replace rather than concatenate:
this.setState({
jsonReturnedValue: json.printCategory.products,
}, () => console.log(this.state));
Related
I am fetching my data from external API as usual and this is the typical way I do it:
Fetch API:
const [tshirts, setTshirts] = useState([]);
const fetchData = () => {
fetch('apiEndpoint')
.then((response) => response.json())
.then((data) => {
setTshirts(data[0].clothes.regular.top); // path to my array
})
.catch((error) => {
console.log(error);
});
};
React.useEffect(() => {
fetchData();
}, []);
Map through an array:
const tshirtArray = tshirts.tShirt; // specifying the path
const listItems = tshirtArray.map((item) => <li>{item}</li>);
<ul>{listItems}</ul>
Example of data structure:
[
{
id: 1,
clothes: {
regular: {
top: {
sleeveless: [],
tShirt: [
"image-path-here"
],
.....
.....
.....
When I first time execute the code it works, but after some time or after refreshing the page I get an error of TypeError: Cannot read properties of undefined (reading 'map')
Why is that undefined? The path is correct and fetching the array should be as well. Can not find the reason of it not working.
I don't have reputation to comment, so let me try to clarify it for you through an answer. As #sojin mentioned, you cannot use tshirts.Tshirt since your state is of array type and arrays can't be used like objects, meaning that if there was an object of lets say exampleObject = { type: "shirt", color: "white } you could call it with exampleObject.type. Since you have an array of objects in your state (top that you are saving to state is still object which contains tShirt array), you first have to use index (to tell which object you want to use from the state array) and then you can use it like you wanted. For example, in your example there are 1 objects in state array. Array indexes start at 0. So you could do tshirts[0].tShirt to get the tShirt array from that object.
However, I would edit your code a bit. Instead of using tshirtArray constant, just do listItems from your state:
const listItems = tshirts.map((item) => {item.tShirt[0]});
Note: I've just used index 0 here to demonstrate the finding of the first item in tShirt array. If you want to see all tShirt image paths, then you may need to do nested mapping or other similar solutions.
I am working with React.js and YouTube API. I get a collection of objects from the API but I want to add a 'check' field to every object. I used the below code -
await axios.get('https://youtube.googleapis.com/youtube/v3/search', {
params: {
part: 'snippet',
q: sTerm,
type: 'video',
key: KEY
},
})
.then(response => {
let i=0;
response.data.items.forEach(item=>{
response.data.items[i]['check']=true
i++;
})
console.log(response.data.items) //gives correct output with check field
console.log(response.data.items[0].check) //gives undefined instead of true
console.log(response.data.items[0]['check']) //gives undefined instead of true
})
What should I do to access the 'check' field value?
Update: This is my response
Finally what worked for me is creating a new array as suggested by a now deleted answer.
.then((response) => {
myItems=response.data.items.map(
item => ({...item, check: true})
);
console.log(myItems);
You can use javascripts Array.prototype.map instead of forEach to transform every value in your items array:
.then(response =>
response.data.items.map(
item => ({...item, check: true})
)
)
This should return on the top line where you are awaiting the axios call the array of items where each item.check equals true.
My React App displays a grid of names for selection. When I select a name it reads a database and shows rows of data applicable to that name. With each row being an object, which is stored as a single array element.
I have two arrays to contain the objects, one is the 'original data' and the other is the 'modified data'. Then I compare these to see if there has been a change to one of the rows before updating the database.
The arrays are defined so:
constructor(props) {
super(props);
this.state = {
...
detail_data: [],
old_detail_data: []
}
}
When I select a row a call is made to _handleRowClick():
_handleRowClick(i) {
if (i >= 0) {
this.getRecord(i, (err, res) => {
if (!err) {
let detail = res.body;
this.setState({
name_detail: Object.assign({}, detail), // Stores details about the name
selectedIndex: i
})
// Fetch the Career Directions
this.fetchSingleCareerDirections(this.state.detail.animal_code); // Gets the rows
}
})
}
}
fetchSingleCareerDirections(animal_code) {
request.get(`/lookup/career_directions/${animal_code}`, (err, res) => {
let data = [].concat(res.body); // row data
this.setState({
detail_data: [...data], // array 1
old_detail_data: [...data], // array 2
});
});
}
At this point all is well and my data is as expected in detail_data and old_detail_data. So I modify one piece of data in one row, in this case clicking a checkbox (for valid career), but any change to row data has the same effect:
<td>
<input type="checkbox"
checked={item.valid_career == 'Y' ? true : false}
style={{ width: 30 }}
name={"valid_career|" + i}
onChange={(e) => { this._setTableDetail("valid_career", e.target.checked == true ? 'Y' : 'N', i) }}
/>
</td>
Which calls the update routine _setTableDetail() to store a 'Y' or 'N' into the detail_data array:
_setTableDetail(name, value, index) {
let _detail_data = Object.assign([], this.state.detail_data);
_detail_data[index][name] = value;
this.setState({ detail_data: _detail_data });
}
This updates this.state.detail_data as expected. But if I look at this.state.old_detail_data the exact change has also been made to that array. Likewise, as a test, if I modify old_detail_data that updates detail_data.
This MUST be happening because the two arrays both reference the same memory space. But I cannot see how that is happening. My setState() routine, as seen above, does this:
this.setState({
detail_data: [...data],
old_detail_data: [...data],
});
Which, to my understanding, uses the spread operator to create a new array in each instance. So how are these two arrays both referencing the same memory space? Is it something to do with me doing the cloning inside the setState() call maybe?
Thanks very much #Anthony for your comment. You were quite right in where I was going wrong. So although my arrays were unique their contained objects were referencing the same objects in memory.
I modified the code of the fetchSingleCareerDirections() function to resolve the issue:
fetchSingleCareerDirections(animal_code) {
request.get(`/lookup/career_directions/${animal_code}`, (err, res) => {
let data = [].concat(res.body);
this.setState({
detail_data: data.map(row => Object.assign({}, row)), // array 1
old_detail_data: data.map(row => Object.assign({}, row)), // array 2
});
});
}
The program now works perfectly.
Just one point to note. I am using a slightly older version of JavaScript for which the spread operator didn't work (sadly). But testing this elsewhere beforehand I was able to use Anthony's code, as suggested, which is a better more modern approach:
fetchSingleCareerDirections(animal_code) {
request.get(`/lookup/career_directions/${animal_code}`, (err, res) => {
let data = [].concat(res.body);
this.setState({
detail_data: data.map(row => ({ ...row })), // spread operator
old_detail_data: data.map(row => ({ ...row })), // spread operator
});
});
}
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 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