Zustand: change value of parameter in stored object - javascript

I am trying to create a function for a state of rated movies in Zustand.
The state consists of an array of objects, example with two entries:
ratedMovies: [
{ imdbID: "tt0076759", userRating: "5" },
{ imdbID: "tt0080684", userRating: "10" },
]
Below is the function managing ratedMovies changes. Here is where the issue lies. I want it to check whether an object with the same imdbID is present in ratedMovies state. And if so to update the value of it, instead of adding another object with the same imdbID but a new value.
If I try to change the rating of one of the movies from the above state example (with them in the state ofc), I get the IF console check and the app crashes with the error:
TypeError: Cannot create property 'userRating' on number '0'
If the state is empty or I change the rating of other movies, I get the ELSE console check, but they are still not added into the state.
addUserRating: (rating) => {
console.log("rating object", rating)
set((state) => {
if (state.ratedMovies.find((movie) => movie.imdbID === rating.imdbID)) {
console.log("add rating IF")
let index = state.ratedMovies.findIndex(
(movie) => movie.imdbID === rating.imdbID
)
index.userRating = rating.userRating
return [index, ...state.ratedMovies]
} else {
console.log("add rating ELSE")
return [rating, ...state.ratedMovies]
}
})
}
the onChange function on the input where one can rate a movie creates an identical object as in the state array and passes it to the function managing the state of ratedMovies:
const changeUserMovieRating = (event) => {
const movieRating = {
imdbID: modalDetails.imdbID,
userRating: event.target.value,
}
console.log(movieRating)
addUserRating(movieRating)
}
Output of which is:
{imdbID: 'tt0120915', userRating: '2'}
I hope i explained everything clearly, and I will highly appreciate any tips on how to solve this issue, thanks in advance!

Sorry but this whole apprach I had at the time of asking this question had no sense and had to be reworked completely.
I decided not to add the parameter during the fetch, as in another component the same data could be fetched. So I decided to instead keep the value of the 'userRating' in the local storage and if the fetched movie was already once rated by the 'user', the value would be displayed.

Related

Getting error of fetched items after refreshing the page

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.

React useState changes all elements in an array in array of objects

I have 2 states product and variations I call an API and set the values of both state to the API response.
I want the product state to stay as it is and not update
const [product, setProduct] = useState({} as any);
const [variations, setVariations] = useState([] as any);
useEffect(() => {
const getProduct = async () => {
const data = await axios.get("/products?id=4533843820679");
console.log(data);
setProduct(data.data);
// #ts-ignore
setVariations([data.data]);
};
getProduct();
}, []);
In return I map the variations array and return inputs for title, and price and a button to add variations. Adding variations will add another product to variations array. So it just pushes product to variations.
Then I have inputs for title in variation and prices in variation.variants. The problem is with onChange.
When I change the price of one element in variants it changes for all and also changes it for PRODUCT state.
The code can be found here: https://codesandbox.io/s/smoosh-firefly-6n747?file=/src/App.js
Add variations, change prices add another variations and you'll see all issues I'm facing.
It is because of this:
variant.price = e.target.value; // same issue with title
the variant object reference is shared among variations and you are modifying it directly. It is shared because you you made a shallow copy of a variation using ... when adding it.
Here is the solution:
You should update the specific variant object in immutable way (in react you should always update state in immutable way). For that you need to use this as onChange for price:
onChange = {
(e) => {
let updated = variations.map((x) => {
if (x.id === variation.id) {
return {
...x,
variants: x.variants.map((y) => {
if (y.id === variant.id) {
return {
...y,
price: e.target.value
};
}
return y;
})
};
}
return x;
});
setVariations(updated);
}
}
This for onChange for title:
onChange = {
(e) => {
let updated = variations.map((x) => {
if (x.id === variation.id) {
return {
...x,
title: e.target.value
};
}
return x;
});
setVariations(updated);
}
}
NOTE but ids of variations must be different. For testing purposes you can use this as click handler when adding a new variation:
onClick = {
() => {
setVariations((prev) => [...prev, {
...product,
id: Math.floor(Math.random() * 1000) // for testing
}]);
}
}
First, you are not pushing the product to variations. You are overwriting it.
To push a value to array with useState,
setVariations([...variations, product])
But, if you change the product object, variations also gonna be change because it's the same object. (Maybe, react not gonna re-render it but trust me, it is changed.) If you want to keep it same you need to create new object.
So,
setProduct(data.data);
setVariations([...variations, {...data.data}]);
Now, you can change product. variations not gonna change.
This was because you did a shallow copy of an object.
Try to do like this:
setVariations([...variations, data.data,]);

React -Update a nested object in redux state of an unknown depth

When a user loads a blog post page, a post is fetched and its comments and subcomments are stored in the Redux state with the following structure:
forumPost: {
id: #,
data: {object},
comments: [
{
id: #,
data: {object},
comments: [
{
id: #,
data: {object},
comments: [...]
},
...
]
},{
id: #,
data: {object},
comments: [...]
},
...
}
When a user posts a new comment/subcomment, it is posted to the database and locally added to the Redux state. I am having problems with the second part
I created a function for finding the path to the parent comment of the comment being posted but am having trouble injecting the new comment into the current state.
const findPath = (comments, parentId) => {
let path = [];
const findIndex = (arry, count) => {
for (; count < arry.length; count++) {
if (arry[count].id === parentId) {
path = "forumPost.comments[" + count + "].comments";
return true;
}
if (arry[count].comments && arry[count].comments.length > 0) {
if (findIndex(arry[count].comments, 0)) {
path = path
.split("forumPost")
.join("forumPost.comments[" + count + "]");
return true;
}
}
}
};
findIndex(comments, 0);
return path; //Returns the parent's path as 'forumPost.comments[a].comments[b].comments'
};
I can create an entirely new state, add the new comment, and replace the old state, but doing so is expensive and (as I understand it) makes it impossible for a component to tell if the Redux state (mapped to props) has changed.
So, having done some research I have come to understand that redux treats updating and replacing the state differently and what I want to do is update the state.
https://reactjs.org/docs/update.html
Cleaner/shorter way to update nested state in Redux?
Object spread vs. Object.assign
I’ve tried to use .update(), …spreads, and dot-prop, but haven’t been able to get them to work (most likely because I have been using them wrong)… and that’s why I am here. How do you update the state with a new comment object to one of the comments arrays of an unknown depth?
Also, I now know that its best practice to keep your redux state as shallow as possible. But the current structure is what it is.

Immutable js - Redux-Saga find item by key and update value

I am using redux-saga and immutable.js in my react app.
I have notifications array holds the data of every notification created by redux actions for whole app.
My immutable redux store and notifications array is like this :
global :
notifications:
0:
key: 21339298 // random key to use like id
show: true
type: success
message: Operation was successfull
1: {key..., show...}
...
I want to find a single notification by "key" and update it's "show" value to false.
I read immutable.js documentation but it is very difficult to understand.
So i tried below code snippets. But i didn't get a result.
return state
.updateIn(['notifications',
ns => ns.findIndex(function (item) {
return item.get("key") === action.notificationId;}
), 'show'], false
);
return state
.update(
list.findIndex(function (item) {
return item.get("key") === action.notificationId;
}), function (item) {
return item.set("show", false);
}
);
How can i find an item and update its some value ?
Solution is to seperate find index and update, first finding index and then using it with setIn. Below is the solution.
case NOTIFY_HIDE:
let notifications = state.get('notifications');
let notificationIdx = notifications.findIndex(function (item) {
return item.get('key') === action.notificationId;
});
return state
.setIn(['notifications', notificationIdx, 'show'], false);

How to delete object from array using object property - React

I have a todo list that holds a delete button in a grandchild, that when clicked fires an event in the parent - I am wanting this event to delete the array entry corresponding to the grandchild clicked.
Parent (contains the array and my attempt at the function)
const tasks = [
{ name: 'task1', isComplete: false },
{ name: 'task2', isComplete: true },
{ name: 'task3', isComplete: false },
]
// taskToDelete is the name of the task - doesn't contain an object
deleteTask(taskToDelete) {
this.state.tasks.remove(task => task.name === taskToDelete);
this.setState({ tasks: this.state.tasks });
}
Any help would be appreciated
Two issues there:
You're seeming to try to direct modify this.state.tasks. It's important not to do that, never directly modify this.state or any object on it. See "Do Not Modify State Directly" in the React documentation for state.
You're passing an object to setState that is derived from the current state. It's important never to do that, too. :-) Instead, pass setState a function and use the state object it passes you when calling that function. From "State Updates May Be Asynchronous" in the documentation:
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state... [Instead]...use a second form of setState() that accepts a function rather than an object.
(my emphasis)
I figure your remove on an array was intended to be hypothetical, but for the avoidance of doubt, arrays don't have a remove method. In this case, the best thing to do, since we need a new array, is to use filter to remove all entries that shouldn't still be there.
So:
deleteTask(taskToDelete) {
this.setState(prevState => {
const tasks = prevState.tasks.filter(task => task.name !== taskToDelete);
return { tasks };
});
}
You could simply filter the array :
this.setState(prevState => ({
tasks: prevState.tasks.filter(task => task.name !== 'taskToDelete')
}));
Also when updating based on this.state, its better to use the function form because setState is async.
You can use filter to remove one object from an array following the immutable pattern (filter will create a new array) :
deleteTask(taskToDelete) {
const newTaskArray = this.state.tasks.filter(task => task.name !== taskToDelete);
this.setState({ tasks: newTaskArray });
}
Edit : codepend of the solution : https://codepen.io/Dyo/pen/ZvPoYP
You can implement deleteTask method as below:
deleteTask(taskToDelete) {
this.setState((prevState, props) => {
const tasks = [...prevState.tasks];
const indexOfTaskToDelete = tasks.findIndex(
task => task.name === taskToDelete
);
tasks.splice(indexOfTaskToDelete, 1);
return { tasks };
});
}
A. Find the index of taskToDelete.
B. Then use splice method to delete the item from the collection
C. Then call setState to update the state with tasks.
You can use higher order function Array#filter to delete the task.
let updatedTasks = this.state.tasks.filter(task => task.name !== taskToDelete);
this.setState({ tasks: updatedTasks });
I have followed below steps to delete a particular selected Object from the state array:
Here I am using a list of checkBoxes, when I am selecting a checkBox it will add it in the state array and when it gets de-selected then it will get deleted from the array.
if (checked) {
var tempObject = { checkboxValue: data, label: title }
this.state.checkBoxState.push(resTemp);
} else {
var element = data; //data is coming from different method.
for (let index = 0; index < this.state.checkBoxState.length; index++) {
if (element === this.state.checkBoxState[index].checkboxValue) {
this.state.checkBoxState.splice(index, 1);
}
}
}
I got stuck for this question and I am sharing my solution. Hope it will help you.

Categories