reactJS map function not working as expected - javascript

I have a reactJS application where I am trying to dynamically render some data that I read in with a fetch() promise. This is the code of my application:
import React from 'react';
import '../styles/app.css';
//think of react components as functions
class Testpage2 extends React.Component {
constructor(props) {
super(props);
this.state = {
numberOfRecords: 0,
productArray: [{
barcode: '',
name: ''
}]
};
}
componentDidMount() {
let currentComponent = this;
var recordCount = 0;
var tempData = [];
//Make use of the API not the web service.
let url = "http://wmjwwebapi-dev.us-west-2.elasticbeanstalk.com/api/getdata";
const options = { method: 'GET' };
fetch(url, options)
.then(function(response) {
return response.json();
})
.then(function(myJson) {
if (myJson == undefined)
{
console.log("fetch failed");
}
else
{
//inspect the data that the WebAPI returned
var return_code = myJson[0].return_code;
if (return_code == "Default Return code"){
recordCount = -2;
} else {
tempData = JSON.parse(myJson[0].return_string);
recordCount = tempData.barcode.length;
}
currentComponent.setState(
{
numberOfRecords: recordCount,
productArray: currentComponent.state.productArray.push(
{
name: tempData.name,
barcode: tempData.barcode
})
}
);
}
});
}
render() {
console.log(this.state.productArray);
return (
<div>
{ this.state.productArray.map((prod, index) => <li key={index}>{prod.barcode}</li>)}
</div>
)
}
}
export default Testpage2
and this is the error message that I am getting:
Uncaught (in promise) TypeError: this.state.productArray.map is not a function
at Testpage2.render (testpage2.js:67)
This is the result of the console.log() that I added in the render() function:
I'm not really sure what this error is telling me or how to go about debugging the issue.
Any help is greatly appreciated.
Thank you.

The return type of array.push is the new length of the array aka a number
So you set the state property productArray to a number and then try to call number.map which is not defined
How to fix?
push first and then use that array to set the state
const updatedArray = [...currentComponent.state.productArray]
updatedArray.push({ name: tempData.name, barcode: tempData.barcode })
currentComponent.setState({
numberOfRecords: recordCount,
productArray: updatedArray
}
Resources:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push

According to MDN:
The push() method adds one or more elements to the end of an array and returns the new length of the array.
It appears that your code expects that Array.push() will return the modified array itself:
productArray: currentComponent.state.productArray.push(...
To prevent the state corruption you should do construct the new array separately, before invoking setState().

Array's push() function returns integer, so you cannot call map() function on it. Try to change your function to:
currentComponent.setState({
numberOfRecords: recordCount,
productArray: [...currentComponent.state.productArray, {
name: tempData.name,
barcode: tempData.barcode
}]
})

The JavaScript Array.push method does not return the modified array, it returns the new length of the array, which is a number. Numbers in JavaScript do not have the map method.
You need to do first create a clone of the productArray, then push the new data, and finally set state:
const newProductArray = [...currentComponent.state.productArray]
newProductArray.push({
name: tempData.name,
barcode: tempData.barcode
})
currentComponent.setState(
{
numberOfRecords: recordCount,
productArray: newProductArray
}
)
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push

Related

Setting react state with nested objects from JSON fetch call

I am fetching recipes from a recipe app and id like to insert certain objects from the returning json result onto my state with setstate. I know how to do one of these but im having trouble figuring out how to map the results on to my state. Can anyone help me on this?
The code for the issue is here. I have changed my api key and code for security
componentDidMount() {
let url = `https://api.edamam.com/search?q=banana&app_id=chjhvje1&app_key=b67djhhvhvhaef`;
fetch(url)
.then((response) => {
return response.json();
})
.then((data) => {
let recipeUIState = [ ...this.state.RecipeUI ];
recipeUIState[0].title = data.hits[0].recipe.label;
recipeUIState[0].thumbnail = data.hits[0].recipe.image;
recipeUIState[0].href = data.hits[0].recipe.url;
this.setState({ RecipeUI: recipeUIState });
console.log(data.hits[0].recipe);
});
}
State is as follows-
export default class RecipeUI extends Component {
constructor(props) {
super(props);
this.state = {
food: '',
RecipeUI: [ { title: '' } ]
// thumbnail: '', ingredients: '', href: ''
};
this.search = this.search.bind(this);
}
reponse from API is attached as image
data.hits.forEach(({ recipe }) => {
// We get the original state every before it's updated in the iteration
const recipeUIState = [...this.state.RecipeUI];
// Check if there's an existing recipe with the same title
const idx = recipeUIState.findIndex(r => r.title === recipe.title);
// Helper object to create a recipe from the current iteration
const currentRecipe = {
title: recipe.label,
thumbnail: recipe.image,
href: recipe.url
};
// `findIndex` returns -1 if no entry was found, otherwise it returns the index
if (idx < 0) {
// No existing recipe was found, append the new recipe to the original state
return this.setState({
recipeUIState: [...recipeUIState, ...currentRecipe]
});
}
// Recipe already existed, create a new recipe by overwriting
// the object at the index we found earlier
const newRecipeUIState = {
...recipeUIState[idx],
...currentRecipe
};
// Replace the recipe at found index
recipeUIState[idx] = newRecipeUIState;
this.setState({ recipeUIState });
});
Something like this? could probably be simplified using Array#reduce but I don't feel too comfortable using it.

React JS cannot read property 'map' of undefined

I want to get data from api, and set it in a state, and use it in map to iterate
const result = await API.getSearch(data);
if(result.status === 200){
this.setState({data: result.data, loading: false })
}
const result = await API.getSearch(data); // got data successfully
Then:
this.state.data && this.state.data.length > 0 && this.state.data.data.map(function(value, i){
I know this kind of error mean, map didn't get any data, So I used condition, first I check if state return data, then check if data has length, but still get this error. any solution ?
Look like you haven't initialized data.
So,During the first rendering, it is undefined.
On undefined,map is not exists.Causing this issue.
Declare and define data like this below
this.state = {
data: []
}
OR
constructor(props) {
this.state = {
data: []
}
}
I guess instead of this
this.state.data.data.map(function(value, i){
It should be only one time data
this.state.data.map(function(value, i){
Use destructure to have default values and make sure the inner data is array
const { data : { data = [] } = {} } = { data: { data: ['foo', 'bar']} } ;
console.log(data.map(x => x + "--").join());

Load data in the object?

I am not sure if i'm doing the right approach, I am doing like class style. Is there a way to load data in the object using loadProducts(data) so then I can call orderLines.getItemsType()
const orderProducts = {
loadProducts: function(data) {
//Load data into orderProducts object?
},
getItemsType: function(type) {
// return data
}
};
Usage:
const items = orderProducts.getItemsType(['abc', 'ddd']);
Note: It is for node.js, not for the browser.
First you want to save the products into a property. We will load the property with some dummy data.
We can then filter the data using filter and test if the item is in the products array like this:
const orderProducts = {
// The list of products
products: [],
// The products to load
loadProducts: function(...data) {
this.products.push(...data)
},
// Get items passed in
getItemsType: function(...type) {
return this.products.filter(p => type.includes(p))
}
}
orderProducts.loadProducts('abc', '123', '111', 'ddd')
const items = orderProducts.getItemsType('abc', 'ddd')
console.log(items)
I guess next approach can help you to make it class approach and solving your question:
class OrderProducts {
constructor(data) {
this.data = data;
this.getItemsType = this.getItemsType.bind(this);
}
getItemsType(type) {
// return the data filtering by type
return this.data;
}
}
// usage
const orderProduct = new OrderProduct(data);
const items = orderProduct.getItemsType(['abc', 'ddd']);

React setState only returning last item in a list saved to variable

I am very new to React, currently doing a small project of using GitHub API to return a search result via AJAX and be able to list that result on the screen. Currently I am using a for loop to iterate over the response data and saving that data to a variable, there is most likely a way better way to do this but like I said I am new. I then set the state to the returned data. The issue is in the setState it is only returning the last result saved to the variable and not the entire variable. Listed below is the entire component, any tips or advice would be greatly appreciated. Thank you!
import axios from 'axios';
import * as React from 'react';
class User extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
name: name,
id: '',
userInput: '',
obj: null
};
}
handleSubmit(e: any) {
axios.get('https://api.github.com/users/' + this.state.userInput + '/repos')
.then((response) => {
if (response.data.length > 0) {
console.log('success');
let data1 = JSON.stringify(response.data);
let result = JSON.parse(data1);
for (let key in result) {
let obj = result[key];
let test = obj.name;
console.log(test);
this.setState({
name: name,
id: result[0].id,
obj: test,
userInput: this.state.userInput
})
};
} else {
console.log('else is working');
}
})
.catch((error) => {
console.log('error ');
});
}
render() {
return (
<div>
<form>
<input type="text" onChange={this.handleUserInput.bind(this)} value={this.state.userInput} />
<h1>{this.state.userInput}</h1>
<button type="button" onClick={this.handleSubmit.bind(this)}>Submit</button>
</form>
<h1>Name : {this.state.name}</h1>
<h1> ID : {this.state.id}</h1>
<h1>OBJ : {this.state.obj}</h1>
</div>
);
}
}
export default User;
The result from consoling the variable test gives this console output
console output
However when it is being set in obj by this.state.obj it is only showing the last item as shown here written to page
Every time you call setState, you overwrite the previous state. React tries to intelligently merge the provided (new) state and the previous state, but if there are any key collisions, the previous state (of that key) will be lost. If you want a list of items in your state, you'll have to do something like
let items = [];
for (...) {
items.push({
name: name,
id: result[0].id,
...
});
}
this.setState({ items: items });
following, you can access each item in the list by using this.state.items[someIndex] etc
Adding to #Tyler Sebastian answer,you can do this
let items = [];
for (let key in result) {
let obj = result[key];
let test = obj.name;
console.log(test);
items.push({
name: name,
id: result[0].id,
obj: test,
userInput: this.state.userInput
});
}
this.setState({ items: items });
and, the render section ,i think you can do this:
return (
<div>
<form>
<input type="text" onChange={this.handleUserInput.bind(this)} value={this.state.userInput} />
<h1>{this.state.userInput}</h1>
<button type="button" onClick={this.handleSubmit.bind(this)}>Submit</button>
</form>
{this.state.items.map((item) => {
<h1>Name : {item.name}</h1>
<h1> ID : {item.id}</h1>
<h1>OBJ : {item.obj}</h1>
})}
</div>
);
Thank you for your answers and tips I really appreciate it. What ended up happening is since I was calling setState within the for loop it was actually displaying all of the items but it was in a split second and would stop at the last so it seemed as if it was just showing the last. This is how I ended up fixing it.
handleSubmit(e: any) {
axios.get('https://api.github.com/users/' + this.state.userInput + '/repos')
.then((response) => {
if (response.data.length > 0) {
console.log('success');
let data1 = JSON.stringify(response.data);
let result = JSON.parse(data1);
let list = '';
for(let i of result) {
list += i.name + ' ';
}
console.log(list);
this.setState({
name: result[0].name,
id: result[0].id,
obj: list,
userInput: this.state.userInput
})
} else {
console.log('else is working');
}
})
.catch((error) => {
console.log('error ');
});
}
Definitely is not the best way to do this, but atleast it is working and I can work towards improving it. Thanks a lot for all the help!

Reducer cannot read property 'photos' of undefined? What am I doing wrong?

Here is the initial state of my reducer, and I need to set it up in this way due to some post processing I need to do:
const initialState = {
showAll: {
photos: null
}
}
Basically, I have a page where you see all your photos, and you can tag certain ones as your pinned photos.
Here's part of my reducer logic:
if (state.showAll.photos) {
const showAllState = state.showAll.photos;
showAllState.map(m => {
if (action.payload.id === m.id) {
m.pinned = true;
}
});
showAllAfterPin = showAllState;
} else {
showAllAfterPin = state.showAll.photos;
}
However, I get an error saying cannot read property 'photos' of undefined and I'm not sure what I am doing wrong.
Might be easier to just set your photos in initialState to empty array [] instead of null.
Another thing, your reducer should not mutate your state object.
Doing const showAllState = state.showAll.photos doesn't make it a new object.
Last thing, showAllState.map(...) needs to return an item inside the function body. It will create a new array.
Here's something you can do...
const { photos = [] } = state.showAll;
const updatedPhotos = photos.map(photo => {
if (action.payload.id === photo.id) {
return Object.assign({}, photo, { pinned: true })
}
return photo;
});
// return entire state if this is inside your root reducer
return {
...state,
showAll {
...state.showAll,
photos: updatedPhotos
}
}

Categories

Resources