Setting react state with nested objects from JSON fetch call - javascript

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.

Related

Updating a two level nested object using immutability-helper in react and typescript

I have an array of object called TourStop, which is two level nested. The types are as below.
TourStops = TourStop[]
TourStop = {
suggestions?: StoreSuggestion[]
id: Uuid
}
StoreSuggestion= {
id: Uuid
spaceSuggestions: SpaceSuggestion[]
}
SpaceSuggestion = {
id: Uuid
title: string
}
My goal is to remove a particular StoreSuggestion from a particular TourStop
I have written the following code(uses immutability-helper and hooks)
const [tourStopsArray, setTourStopsArray] = useState(tourStops)
// function to remove the store suggestion
const removeSuggestionFromTourStop = (index: number, tourStopId: Uuid) => {
// find the particular tourStop
const targetTourStop = tourStopsArray.find(arr => arr.id === tourStopId)
// Update the storeSuggestion in that tourstop
const filteredStoreSuggestion = targetTourStop?.suggestions?.filter(sugg => sugg.id !== index)
if (targetTourStop) {
// create a new TourStop with the updated storeSuggestion
const updatedTargetTourStop: TourStopType = {
...targetTourStop,
suggestions: filteredStoreSuggestion,
}
const targetIndex = tourStopsArray.findIndex(
tourStop => tourStop.id == updatedTargetTourStop.id,
)
// Find it by index and remove it
setTourStopsArray(
update(tourStopsArray, {
$splice: [[targetIndex, 1]],
}),
)
// add the new TourStop
setTourStopsArray(
update(tourStopsArray, {
$push: [updatedTargetTourStop],
}),
)
}
}
The push action works correctly. However the splice action doesn't work for some reason. What am I doing wrong?

reactJS map function not working as expected

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

How do I update an array of objects in component state?

I am trying to update the property of an object which is stored in an array.
my state looks something like this:
state = {
todos: [
{
id: '1',
title: 'first item,
completed: false
},
{
id: '2',
title: 'second item,
completed: false
}
],
}
What I am trying to do is access the second element in the 'todos' array and update the completed property to either false -> true or true -> false.
I have a button with the handler for update, and my class method for the update looks like this:
onUpdate = (id) => {
const { todos } = this.state;
let i = todos.findIndex(todo => todo.id === id);
let status = todos[i].completed
let updatedTodo = {
...todos[i],
completed: !status
}
this.setState({
todos: [
...todos.slice(0, i),
updatedTodo,
...todos.slice(i + 1)
]
});
}
While this does work, I want to find out if there is a more concise way of achieving the same result; I tried to use Object.assign(), but that didn't work out because my 'todos' is an array, not an object. Please enlighten me with better code!
It would be best to use update function to make sure you don't work on outdated data:
onUpdate = (id) => {
this.setState(prevState => {
const copy = [...prevState.todos];
const index = copy.findIndex(t => t.id === id);
copy[index].completed = !copy[index].completed;
return { todos: copy }
})
}
You can simply copy your todos from state, then make edits, and after that put it back to the state
onUpdate = (id) => {
var todos = [...this.state.todos]
var target = todos.find(todo => todo.id == id)
if (target) {
target.completed = !target.completed
this.setState({ todos })
}
}

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']);

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