Properly returning nested state with Object.assign - javascript

I'm working on a React/Redux application and for the most part, everything has been working smoothly.
Essentially, it's a todo application that has categorization built in.
I'm having trouble properly returning the full existing state in my reducer when the user adds a todo-item inside a category.
The redux state before I dispatch the ADD_ITEM action looks like this:
{
items: {
"HOME": [["Do laundry", "High Priority"],["Feed kids", "Low priority"] ],
"WORK": [["Get promotion", "High priority"],["Finish project", "Medium priority"] ],
"BOTH": [["Eat better", "Medium priority"],["Go for a run", "High priority"] ],
},
settings: {
Test: "test"
}
}
The user navigates to a category(pre-made, haven't implemented creating them yet) and can create a new todo-item with a name and a priority. This dispatches an action that returns an array like [category, name, priority].
Currently in my reducer, I have it where it is successfully adding the item, but it is emptying/overwriting all the existing categories.
My reducer looks like this:
case types.ADD_ITEM:
let cat = action.payload[0];
let name = action.payload[1];
let priority = action.payload[2];
return Object.assign({}, state, { items: { [cat]: [...state.items[cat], [name, priority]]}});
I've tried creating a new object first with all the combined items like so:
let combinedItems = Object.assign({}, state.items, { [cat]: [...state.items[cat], action.payload] });
If I console.log the above combinedItems, I get the exact object that I want items to be. However, I'm struggling to have the final object returned by the reducer to reflect that.
When I tried something like below, I got an object that contained combinedItems as a separate key inside items.
return Object.assign({}, state, { items: { combinedItems, [cat]: [...state.items[cat], [name, priority]]}});
Can anyone help me get my final redux state to contain all the existing categories/items + the user added one? I would really appreciate the help.

I think you should use objects in places where you have arrays. In your action payload, instead of:
[category, name, priority]
You can have:
{category, name, priority}
action.payload.category
I would make the same change with your todo items. Instead of:
[["Eat better", "Medium priority"], ... ]
You can have:
[{ name: "Eat better", priority: "Medium" }, ... ]
Now in terms of whether it's better to make items an object with category keys or an array of items that know its category... well I think the latter is better, that way if you get a single item, you don't need to go up to its parent to find out which category it belongs to. It would also make your problem a bit more manageable.
items: [
{
name: "Eat better",
priority: "Medium",
category: "Both"
}, ...
]
Putting this all together to solve your problem:
case types.ADD_ITEM:
let newItem = {
name: action.payload.name,
priority: action.payload.priority,
category: action.payload.category
}
return Object.assign({}, state, { items: [ ...state.items, newItem ] })
Whatever benefit you had before with categories as keys are trivial to reproduce with this structure.
Get all items in the HOME category:
this.props.items.filter(item => item.category === 'HOME')

Related

update data with useState in React js

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.

Update object within object using Redux action

I'm making a kind of twitter-style clone using React and Redux. I currently have an object of posts ("lings"), that each contain a nested object of replies. They look like this:
{
id: 5,
userName: "Gordon Maloney",
lingBody: "this is a ling",
lingRepliesObj: [
{
replyId: 0,
replyBody: "this is a reply",
},
{
replyId: 1,
replyBody: "this is a second reply",
},
],
},
I'm trying to build an action that allows users to add a reply, by adding to that nested object. The action pushes the ID of the post being replied to, and then the reply itself - when I console log them, it comes through fine, but I just cannot for the life of me work out:~
a) how to be adding to the right item in the object - it's being passed to the action as "parentId" - but I can't work out how to get it from there into the action reducer
b) I am also definitely doing something wrong trying to add the action payload into the array itself.
The action reducer looks like this (currently just pushing the array index of 5 arbitrarily, but that's what I'm wanting to replace with the parentId)
case ActionTypes.POST_REPLY:
console.log(...state.lings[5].lingRepliesObj);
console.log(action.payload);
return {
...state,
lings: [...state.lings[5].lingRepliesObj, {...action.payload}],
};
And the action itself like this:
export const postReply = (values) => {
console.log("You posted a new reply: ", values);
return {
type: "POST_REPLY",
payload: {
replyId: "new-id",
replyAuthor: "reply tester",
replyType: "correction",
correctionBody: values.replyCorrection,
replyBody: values.replyReply,
},
};
};
Am I even close? šŸ˜£
My GitHub for it is here: https://github.com/gordonmaloney/Lingr and the relevant files are:
src/components/actions/newReplyAction
src/components/reducers/lings
src/components/LingReply

How to access an attribute in an object array in Redux state?

I'm very new to Redux, and confused as to how to update nested state.
const initialState = {
feature: '',
scenarios: [{
description: '',
steps: []
}]
}
I know that to just push to an array in an immutable way, we could do,
state = {
scenarios: [...state.scenarios, action.payload]
}
And to push into a specific attribute, as this SO answer suggests
How to access array (object) elements in redux state, we could do
state.scenarios[0].description = action.payload
But my question is, how would we access a particular attribute in an object array without mentioning the index? is there a way for us to push it to the last empty element?
All suggestions are welcome to help me understand, thank you in advance :)
Redux helps to decouple your state transformations and the way you render your data.
Modifying your array only happens inside your reducers. To specify which scenario's description you want to modify is easy to achieve by using an identifier. If your scenario has in id, it should be included in your action, e.g.
{
"type": "update_scenario_description",
"payload": {
"scenario": "your-id",
"description": "New content here"
}
}
You can have a reducer per scenario. The higher level reducer for all scenarios can forward the action to the specific reducer based on the scenario id, so that only this scenario will be updated.
In your ui, you can use the array of scenarios and your scenario id to render only the specific one you're currently viewing.
For a more detailed explanation, have a look at the todo example. This is basically the same, as each todo has an id, you have one reducer for all todos and a specific reducer per todo, which is handled by it's id.
In addition to the accepted answer, I'd like to mention something in case someone still wants to "access a particular attribute in an object array without mentioning the index".
'use strict'
const initialState = {
feature: '',
scenarios: [{
description: '',
steps: []
}]
}
let blank = {}
Object.keys(initialState.scenarios[0]).map(scene => {
if (scene === 'steps'){
blank[scene] = [1, 2]
} else {
blank[scene]=initialState.scenarios[0][scene]
}
})
const finalState = {
...initialState,
scenarios: blank
}
console.log(initialState)
console.log(finalState)
However, if scenarios property of initialState instead of being an object inside an array, had it been a simple object like scenarios:{description:'', steps: []}, the solution would have been much simpler:
'use strict'
const initialState = {
feature: '',
scenarios: {
description: '',
steps: []
}
}
const finalState = {
...initialState,
scenarios: {
...initialState.scenarios, steps: [1, 2, 4]
}
}
console.log(initialState)
console.log(finalState)

How to update the value of a single property within a state object in React.js?

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.

Updating nested data in redux store

What's the best/correct way to update a nested array of data in a store using redux?
My store looks like this:
{
items:{
1: {
id: 1,
key: "value",
links: [
{
id: 10001
data: "some more stuff"
},
...
]
},
...
}
}
I have a pair of asynchronous actions that updates the complete items object but I have another pair of actions that I want to update a specific links array.
My reducer currently looks like this but I'm not sure if this is the correct approach:
switch (action.type) {
case RESOURCE_TYPE_LINK_ADD_SUCCESS:
// TODO: check whether the following is acceptable or should we create a new one?
state.items[action.resourceTypeId].isSourceOf.push(action.resourceTypeLink);
return Object.assign({}, state, {
items: state.items,
});
}
Jonny's answer is correct (never mutate the state given to you!) but I wanted to add another point to it. If all your objects have IDs, it's generally a bad idea to keep the state shape nested.
This:
{
items: {
1: {
id: 1,
links: [{
id: 10001
}]
}
}
}
is a shape that is hard to update.
It doesn't have to be this way! You can instead store it like this:
{
items: {
1: {
id: 1,
links: [10001]
}
},
links: {
10001: {
id: 10001
}
}
}
This is much easier for update because there is just one canonical copy of any entity. If you need to let user ā€œedit a linkā€, there is just one place where it needs to be updatedā€”and it's completely independent of items or anything other referring to links.
To get your API responses into such a shape, you can use normalizr. Once your entities inside the server actions are normalized, you can write a simple reducer that merges them into the current state:
import merge from 'lodash/object/merge';
function entities(state = { items: {}, links: {} }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities);
}
return state;
}
Please see Redux real-world example for a demo of such approach.
React's update() immutability helper is a convenient way to create an updated version of a plain old JavaScript object without mutating it.
You give it the source object to be updated and an object describing paths to the pieces which need to be updated and changes that need to be made.
e.g., if an action had id and link properties and you wanted to push the link to an array of links in an item keyed with the id:
var update = require('react/lib/update')
// ...
return update(state, {
items: {
[action.id]: {
links: {$push: action.link}
}
}
})
(Example uses an ES6 computed property name for action.id)

Categories

Resources