I am new to react. I have faced one issue and not able to solve it. I am looking for your help.
I have an array which I have listed below. All data are looped and displayed in the view. From my current array, I want to update the count of dietry array[] which is inside the fruits array.
This is my useState
const [foods, setFood] = useState(fruits)
if I console.log(foods) it gives data as below.
fruits: [
{
id: 1,
name: 'Banana',
family: 'abc',
price: 2.99,
isEnabled: true,
dietary: [
{
id:1,
disabled: false,
group: null,
selected: false,
text: 'N/A',
value: '858090000',
count:0
},
{
id:2,
disabled: true,
group: null,
selected: true,
text: 'N/A',
value: '80000',
count:0
},
}
This data are looped in a view page and there is onClick handleIncrement method which should increment the count of dietary array of index 0 or index1 etc whichever index sent from handleIncremnt() method.
This is my method
const handleIncrementCount = (dietary_index) => {
setFood(foods =>
foods.map((food,index) =>
dietary_index === food[index] ? {...food, qty:food.count+1}:food
)
);
}
I am not able to increase the count, in my view page counts are 0 even if i click the increment button.It shows some error within map
Any help is highly appreciated
I ma looking for a solutions
There are a few issues with your handleIncrementCount function. Firstly, you are trying to use the dietary_id parameter to find the correct food object to update, but you are using it to access the index property of the food object instead. This will always return undefined, and so the function will not be able to find the correct object to update.
Secondly, you are trying to update the qty property of the food object, but this property does not exist in the food object. Instead, you need to update the count property of the correct dietary object inside the food object.
Here is how you can fix these issues:
const handleIncrementCount = (dietary_id) => {
setFood(foods =>
foods.map(food => {
// Find the dietary object with the correct id
const dietary = food.dietary.find(d => d.id === dietary_id);
// If the dietary object was found, increment its count
if (dietary) {
dietary.count += 1;
}
// Return the updated food object
return food;
})
);
};
With this change, the handleIncrementCount function should be able to find the correct food object and update the count property of the correct dietary object.
Note that this function is using the Array.prototype.map() and Array.prototype.find() methods to transform the foods array and find the correct dietary object to update. These methods are commonly used in JavaScript to transform and find elements in arrays. It is worth reading up on these methods and understanding how they work in order to better understand this code.
Related
When reading React Cookbook I've stumbled upon a code snippet, this function gets called when user checks a task as completed in a TODO list:
markAsCompleted = id => {
// Finding the task by id...
const foundTask = this.state.items.find(task => task.id === id);
// Updating the completed status...
foundTask.completed = true;
// Updating the state with the new updated task...
this.setState({
items: [
...this.state.items,
...foundTask
]
});
}
UPD: Somehow I've completely missed the spread operator on foundTask. So what's really happening is the state gets updated with only ...this.state.items (which was mutated), and the ...foundTask part does not go into state, since it's not a valid spread.
At first it looked like it should add a new element to the items array, instead of updating, so I went to the JS console to check:
state = { items: [{id: '0', done: false}, {id: '1', done: false}] }
upd = state.items.find(obj => obj.id === '0') // {id: "0", done: false}
upd.done = true // also updates object inside the array
state = { items: [...state.items, upd] }
/* Indeed, adds a new element:
items: Array(3)
0: {id: "0", done: true}
1: {id: "1", done: false}
2: {id: "0", done: true}
*/
So then I've downloaded the code and ran it locally. And, to my surprise, it worked! State was getting updated without any trouble, no extra elements appeared. I used React DevTools to see the live state while testing.
I searched the web, but couldn't find any examples like in the book, but with a better explanation. Usually all solutions involve using .map() to build a new array, and then replace an existing one (e.g. https://stackoverflow.com/a/44524507/10304479).
The only difference I see between the book code snippet and console test is that React is using .setState(), so maybe this helps somehow. Can anyone help to clarify, why is it working?
Thanks!
Array.find will return the first value matched in the array. Here the array consist of objects and the value returned will be the reference to the object.
const foundTask = this.state.items.find(task => task.id === id);
Here foundTask will have reference to the same object contained in the state.items. So when you modify foundTask you're modifying the same object as in state.items.
For example,
If this.state.items is [{ id: 1 }] and if you do
const foundTask = this.state.items.find(obj => obj.id === 1);
foundTask.id = 2;
console.log(this.state.items); // [{ id:2 }]
In the code,
this.setState({
items: [
...this.state.items,
...foundTask
]
});
This will update the state with updated completed value for the task.
...foundTask will give you an error in the console since foundTask will be an object and you're spreading it in an array.
Here not having ...foundTask will produce same result. Perhaps not with an error.
I have a react application, where I use the axios library, to get some values, and set them into an array of javascript objects in my state
componentDidMount(){
axios.get('http://localhost:8080/zoo/api/animals')
.then(res => this.setState({animals: res.data}))
}
Now I want to check if the objects, contains an Owner object, inside it, and filter out does that does,
First, I tried making a const, and then using the filter, to check if they contain the objects, and then set the state, but I can't save my values in a local variable
componentDidMount(){
const animals= [];
axios.get('http://localhost:8080/zoo/api/animals')
.then(res => animals=res.data)
console.log(animals) // logs empty array
console.log('mounted')
}
how can I make it so, that I can only get the animals that do NOT, have an owner object inside it?
Your animal array is empty in your second example because axios.get is asynchronous, what is in your then will be executed once the data is fetch, but the function will keep on going in the meantime.
To filter out your array, simply use filter right after fetching your data :
componentDidMount(){
axios.get('http://localhost:8080/zoo/api/animals')
.then(res => this.setState({animals: res.data.filter(animal => !animal.owner)}))
}
This function will filter out every animal object that does not have an owner property.
Working example :
const animals = [
{
name: 'Simba',
owner: {
some: 'stuff'
}
},
{
name: 1
}, ,
{
name: 2
}, ,
{
name: 3,
owner: {
some: 'stuff'
}
},
{
name: 'Bambi'
//status: 'dead'
}
]
console.log(animals.filter(animal => animal.owner))
EDIT: the answer was changed so that it only filters animals, that does not have an owner
So I have the following object structure:
const SamplePalette = {
id: 1,
name: "Sample Palette",
description: "this is a short description",
swatches: [
{
val: "#FF6245",
tints: ["#FFE0DB", "#FFA797"],
shades: ["#751408", "#C33F27"]
},
{
val: "#FFFDA4",
tints: ["#FFFFE1"],
shades: ["#CCCB83"]
},
{
val: "#BFE8A3",
tints: ["#E7FFD7"],
shades: ["#95B77E"]
}
]
}
Let's imagine that this object is managed by the state of my app like this:
this.state = {
currentPalette: SamplePalette,
}
My question is how would I go about updating the val property of a given swatch object in the swatches array? Or more generally - how do I only update pieces of this object?
I tried using the update helper as well as to figure out how Object.assign() works, however I've been unsuccessful and frankly can't really grasp the syntax by just looking at examples.
Also, since I'm going to be modifying this object quite a lot, should I look into maybe using Redux?
[EDIT]
I tried #maxim.sh suggestion but with no success:
this.setState(
{ currentPalette: {...this.state.currentPalette,
swatches[0].val: newValue}
})
Consider you have new new_swatches
I think the clearer way is to get array, update it and put back as:
let new_swatches = this.state.currentPalette.swatches;
new_swatches[0].val = newValue;
this.setState(
{ currentPalette:
{ ...this.state.currentPalette, swatches: new_swatches }
});
Also you have : Immutability Helpers or https://github.com/kolodny/immutability-helper
Available Commands
{$push: array} push() all the items in array on the target.
{$unshift: array} unshift() all the items in array on the target.
{$splice: array of arrays} for each item in arrays call splice() on the target with the parameters provided by the item.
{$set: any} replace the target entirely.
{$merge: object} merge the keys of object with the target.
{$apply: function} passes in the current value to the function and updates it with the new returned value.
I have an immutable List that looks like this:
this.state = {
suggestedUsers: fromJS([
{
user: {
client_user_id: "1234567890",
full_name: "marty mcfly",
image: "imageURL",
role_name: "Associate Graphic Designer",
selected: false
}
},
{
user: {
client_user_id: "0987654321",
full_name: "doc",
image: "imageURL",
role_name: "Software Engineer",
selected: false
}
}
)]
This is used in a div that displays this information in the UI.
When I click on the div, I have a function that is fired that looks like this:
selectUser(clientUserId){
// set assessments variable equal to the current team from the state
let assessments = fromJS(this.state.suggestedUsers)
let selectAssessor
// set a variable called selectedUsers equal to the results of filtering over the current suggestedUsers from the state
let selectedUsers = assessments.filter((obj) => {
// store immutable retrieval of the client user id in a variable called userId
let userId = obj.getIn(["user", "client_user_id"])
// when the user clicks 'Add' user, if the id of the user matches the selected user id
// the user, represented here by obj, is pushed into the selectedUsers array stored in the state.
if(userId === clientUserId){
return obj.setIn(["user", "selected"], true)
}
// If the user id is not equal to the selected user, that team member is kept in the
// current team array represented by the state.
return userId !== clientUserId
})
// update the state with the current representation of the state determined by the user
// selected team members for assessment requests
this.setState({
suggestedUsers: selectedUsers
})
}
The core of my question is this:
I would like to update the value of the 'selected' key in the users object to false, when this function is invoked.
I'm aware that I can't mutate the List I'm filtering over directly, but I've tried may different approaches to getting the selected value updated (i.e. using updateIn, and setIn). I know I need to set the result of calling setIn to a variable, and return that to the List I'm filtering over, but I can't get the value to update in the existing List. Any help is greatly appreciated. Thanks!
I've verified that this works the way it should when I change the value manually. How can I change it with immutable by updating this one List.
=========================================================================
Thank you to the community for your feedback. Filtering, and mapping did turn out to be overkill. Using immutability-helper, I am able to update the selected value of a particular user at the index that is clicked. One caveat that was not mentioned is using merge to bring your updated data into your previous data. After updating with immutability helper, I push the updated value into an array, then make it a List, and merge it into my original data. Code below:
let users = this.state.teamAssessments
let selectedArray = []
users.map((obj, index) => {
let objId = obj.getIn(["user", "client_user_id"])
if(objId === clientUserId){
const selectedUser = update(this.state.teamAssessments.toJS(), {
[index]: {
user : {
selected: {
$set: true
}
}
}
})
selectedArray.push(selectedUser)
}
})
let updatedArray = fromJS(selectedArray).get(0)
let mergedData = users.merge(updatedArray)
this.setState({
teamAssessments: mergedData
})
You need immutability-helper. Basically, instead of cloning the entire object you just modify small pieces of the object and re-set the state after you are finished.
import update from 'immutability-helper';
const newData = update(myData, {
x: {y: {z: {$set: 7}}},
a: {b: {$push: [9]}}
});
this.setState({varName: newData});
In other words, I would ditch the fromJS and the modifying of the array while enumerating it. First, enumerate the array and create your updates. Then, apply the updates separately. Also, to me the "selected" var seems redundant as you know if they are selected because the name of the array after filtration is "selectedUsers."
If I understand your question correctly, here's what I would suggest:
selectUser(clientUserId) {
let suggestedUsers = this.state.suggestedUsers.map(
userBlock =>
userBlock.setIn(
['user', 'selected'],
userBlock.getIn(['user', 'client_user_id']) === clientUserId
)
);
this.setState({
suggestedUsers,
});
}
To confirm -- you are just trying to modify state.suggestedUsers to have selected be true for the selected user, and false for everyone else? Sounds perfect for Immutable's map function (rather than filter, which will just return the elements of the list for which your predicate function returns something truthy).
BTW, you have an error in your object passed into fromJS -- you need an extra }, after the first assessor_assessment block.
I have a todo list and want to set the state of that item in the array to "complete" if the user clicks on "complete".
Here is my action:
export function completeTodo(id) {
return {
type: "COMPLETE_TASK",
completed: true,
id
}
}
Here is my reducer:
case "COMPLETE_TASK": {
return {...state,
todos: [{
completed: action.completed
}]
}
}
The problem I'm having is the new state does no longer have the text associated of that todo item on the selected item and the ID is no longer there. Is this because I am overwriting the state and ignoring the previous properties? My object item onload looks like this:
Objecttodos: Array[1]
0: Object
completed: false
id: 0
text: "Initial todo"
__proto__: Object
length: 1
__proto__: Array[0]
__proto__: Object
As you can see, all I want to do is set the completed value to true.
You need to transform your todos array to have the appropriate item updated. Array.map is the simplest way to do this:
case "COMPLETE_TASK":
return {
...state,
todos: state.todos.map(todo => todo.id === action.id ?
// transform the one with a matching id
{ ...todo, completed: action.completed } :
// otherwise return original todo
todo
)
};
There are libraries to help you with this kind of deep state update. You can find a list of such libraries here: https://github.com/markerikson/redux-ecosystem-links/blob/master/immutable-data.md#immutable-update-utilities
Personally, I use ImmutableJS (https://facebook.github.io/immutable-js/) which solves the issue with its updateIn and setIn methods (which are more efficient than normal objects and arrays for large objects with lots of keys and for arrays, but slower for small ones).
New state does no longer have the text associated of that todo item on
the selected item and the ID is no longer there, Is this because I am
overwriting the state and ignoring the previous properties?
Yes, because during each update you are assigning a new array with only one key completed, and that array doesn't contain any previous values. So after update array will have no previous data. That's why text and id's are not there after update.
Solutions:
1- Use array.map to find the correct element then update the value, Like this:
case "COMPLETE_TASK":
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.id ? { ...todo, completed: action.completed } : todo
)
};
2- Use array.findIndex to find the index of that particular object then update that, Like this:
case "COMPLETE_TASK":
let index = state.todos.findIndex(todo => todo.id === action.id);
let todos = [...state.todos];
todos[index] = {...todos[index], completed: action.completed};
return {...state, todos}
Check this snippet you will get a better idea about the mistake you are doing:
let state = {
a: 1,
arr: [
{text:1, id:1, completed: true},
{text:2, id:2, completed: false}
]
}
console.log('old values', JSON.stringify(state));
// updating the values
let newState = {
...state,
arr: [{completed: true}]
}
console.log('new state = ', newState);
One of the seminal design principles in React is "Don't mutate state." If you want to change data in an array, you want to create a new array with the changed value(s).
For example, I have an array of results in state. Initially I'm just setting values to 0 for each index in my constructor.
this.state = {
index:0,
question: this.props.test[0].questions[0],
results:[[0,0],[1,0],[2,0],[3,0],[4,0],[5,0]],
complete: false
};
Later on, I want to update a value in the array. But I'm not changing it in the state object. With ES6, we can use the spread operator. The array slice method returns a new array, it will not change the existing array.
updateArray = (list, index,score) => {
// updates the results array without mutating it
return [
...list.slice(0, index),
list[index][1] = score,
...list.slice(index + 1)
];
};
When I want to update an item in the array, I call updateArray and set the state in one go:
this.setState({
index:newIndex,
question:this.props.test[0].questions[newIndex],
results:this.updateArray(this.state.results, index, score)
});