How to use reduce function in a nested state object in reactJS? - javascript

My state value is
this.state = {
content: {
text: {
tag1: {
line: "data1"
}
tag2: {
line: "data2"
}
}
}
}
How can I use javascript reduce() function to change the value of line of both tag1 and tag2 to "changed text"?

Here you are:
this.setState(prevState => {
return {
content: {
...prevState.content,
text: Object.keys(prevState.content.text).reduce((newTexts, key) => {
return {
...newTexts,
[key]: {
line: "changed text"
}
}
}, {})
}
}
});

You should you setState with a function so you don't change state directly.
this.setState(prevState => {
for(let k in prevState.content.text){
prevState.content.text[k].line = "changed";
}
return {content: prevState.content}
}
Edit:
I'm not sure if changing prevState directly is a good thing (please some one correct me), but you can also do
this.setState(prevState => {
let changedState = {...prevState}
for(let k in changedState.content.text){
changedState.content.text[k].line = "changed";
}
return {content: changedState.content}
}
Edit:
As said in the comments, {...prevState} is going to be a shallow copy and it can still change the state directly. One solution to this is use lodash cloneDeep

I think using for..in will be better.
const state = {
content: {
text: {
tag1: {
line: "data1"
},
tag2: {
line: "data2"
}
}
}
}
for(let k in state.content.text){
state.content.text[k].line = "changed";
}
console.log(state)

I don't think Array#prototype#reduce would be a fit.
You can use a plain modern for...of loop with Object.entries and do this:
const state = {
content: {
text: {
tag1: {
line: "data1"
},
tag2: {
line: "data2"
}
}
}
};
for (const obj of Object.entries(state.content.text)) {
obj[1].line = 'Changed text';
}
console.log(state);
For acheiving an immutable state you can do Object.assign before mutating the new object's properties.
const state = {
content: {
text: {
tag1: {
line: "data1"
},
tag2: {
line: "data2"
}
}
}
};
// Create a new object from an existing object
const newState = Object.assign(state, {});
Object.entries(newState.content.text).forEach(x => {
x[1].line = 'Changed text';
});
console.log(newState);

Related

Double for loop without mutating prop, VUE3

I have a 'data' props which say looks like this:
data = [
{
"label":"gender",
"options":[
{"text":"m","value":0},
{"text":"f","value":1},
{"text":"x", "value":null}
]
},
{
"label":"age",
"options":[
{"text":"<30", "value":0},
{"text":"<50","value":1},
{"text":">50","value":3}
]
}
]
In a computed property I want to have a new array which looks exactly like the data prop, with the difference that - for the sake of example let's say - I want to multiply the value in the options array by 2. In plain js I did this before, like this:
data.forEach(item => {
item.options.forEach(option => {
if (option.value !== null && option.value !== 0) {
option.value *= 2;
}
})
});
Now I'm trying to do this in a computed property, with .map(), so it doesn't mutate my data props, but I cant figure out how.
computed: {
doubledValues() {
var array = this.data.map((item) => {
//...
item.options.map((option) => {
//... option.value * 2;
});
});
return array;
}
}
you can use map() method, like so:
computed: {
doubledValues() {
return this.data.map(item => ({...item, options: item.options.map(obj => {
return (obj.value != null) ? { ...obj, value: obj.value * 2 } : { ...obj }
})})
);
}
}
Just copy objects/arrays. It will be something like that
computed: {
doubledValues() {
return this.data.map((item) => {
const resultItem = {...item};
resultItem.options = item.options.map((option) => {
const copyOption = {...option};
if (copyOption.value !== null && copyOption.value !== 0) {
copyOption.value *= 2;
}
return copyOption;
});
return resultItem;
});
}
}

How to append the object into existing json array of objects

I am having json object like below which will be dynamic,
let data_existing= [
{
"client":[
{
"name":"aaaa",
"filter":{
"name":"123456"
}
}
]
},
{
"server":[
{
"name":"qqqqq",
"filter":{
"name":"984567"
}
}
]
},
]
From the inputs i will get an object like below,
let data_new = {
"client":[
{
"name":"bbbbb",
"filter":{
"name":"456789"
}
}
]
}
I need to append this object into the existing "client" json object. Expected output will be like,
[
{
"client":[
{
"name":"aaaa",
"filter":{
"name":"123456"
}
},
{
"name":"bbbb",
"filter":{
"name":"456789"
}
}
]
},
{
"server":[
{
"name":"qqqqq",
"filter":{
"name":"984567"
}
}
]
}
]
And, if the "data_new" is not exists in the main objects, it should as new objects like below, for example,
let data_new = {
"server2":[
{
"name":"kkkkk",
"filter":{
"name":"111111"
}
}
]
}
output will be like,
[
{
"client":[
{
"name":"aaaa",
"filter":{
"name":"123456"
}
},
]
},
{
"server":[
{
"name":"qqqqq",
"filter":{
"name":"984567"
}
}
]
},
{
"server2":[
{
"name":"kkkkk",
"filter":{
"name":"11111"
}
}
]
}
]
I tried the below method, but it is not working as expected. Some help would be appreciated.
Tried like below and not worked as expected,
function addData(oldData, newData) {
let [key, value] = Object.entries(newData)[0]
return oldData.reduce((op, inp) => {
if (inp.hasOwnProperty(key)) {
console.log("111");
op[key] = inp[key].concat(newData[key]);
} else {
console.log(JSON.stringify(inp));
op = Object.assign(op, inp);
}
return op
}, {})
}
Your function seems to work when the key already belongs to data_existing (e.g.: "client").
But you have to handle the second use-case: when the key was not found in the objects of data_existing (e.g.: "server2").
This shall be performed after the reduce loop, adding the new item to data_existing if the key was not found.
Here is an example of how you could achieve that:
function addData(inputData, inputItem) {
const [newKey, newValue] = Object.entries(inputItem)[0];
let wasFound = false; // true iif the key was found in list
const res = inputData.reduce((accumulator, item) => {
const [key, value] = Object.entries(item)[0];
const keyMatch = key === newKey;
if (keyMatch) {
wasFound = true;
}
// concatenate the lists in case of key matching
const newItem = { [key]: keyMatch ? [...value, ...newValue] : value };
return [...accumulator, newItem];
}, []);
if (!wasFound) {
res.push(inputItem); // if key was not found, add item to the list
}
return res;
}
Hope it helps.

Need a custom assignment implementaion

I am working with some state management application where I have a data structure as follows
const mainObject = {
firstLevel: {
secondLevel: {
thirdLevel: {
actualProperty: 'Secret'
}
}
},
firstLevelUntouched:{
secondLevelUntouched:{
thirdLevelUntouched:{
untouchedProperty:'I don`t want to change'
}
}
}
};
I want to change the actualProperty to a new value which out a deepClone
I did it with the following code
const modified = {
...mainObject,
...{
firstLevel: {
...mainObject.firstLevel,
...{
secondLevel: {
...mainObject.firstLevel.secondLevel,
thirdLevel: {
...mainObject.firstLevel.secondLevel.thirdLevel,
actualProperty: 'New secret'
}
}
}
}
}
}
But its looks like Bulky Code. So I need to write a function like
modified = myCustomAssignment(mainObject, ['firstLevel', 'secondLevel', 'thirdLevel', 'actualProperty'], 'New secret')
Can anyone help me on this?
You could use a simple traversal function for this that just traverses the passed properties until it arrives as the final one, then sets that to the new value.
function myCustomAssignment(mainObject, propertyList, newValue) {
const lastProp = propertyList.pop();
const propertyTree = propertyList.reduce((obj, prop) => obj[prop], mainObject);
propertyTree[lastProp] = newValue;
}
You could even add propertyList = propertyList.split('.') to the top of this function so the list can be passed in as an easy-to-read string, like myCustomAssignment(mainObject, 'firstLevel.secondLevel.thirdLevel.actualProperty', 'new value') if you wanted that.
export function mutateState(mainObject: object, propertyList: string[], newValue: any) {
const lastProp = propertyList.pop();
const newState: object = { ...mainObject };
const propertyTree =
propertyList
.reduce((obj, prop) => {
obj[prop] = { ...newState[prop], ...obj[prop] };
return obj[prop];
}, newState);
propertyTree[lastProp] = newValue;
return newState as unknown;
}
This fixed my issue. thanks all..

How to filter on properties of children object and return the parent with children that passes the filter in Javascript?

I want to filter on the property of children object and return parents with children that passes the filter.
I tried with combination of Array.filter, Array.some, and Object.values, but I can't think of a way to get the key back once Ive used Object.values
var data = {
parent1: {
child1: {
source: true
},
child2: {
source: true
}
},
parent2: {
child3: {
source: true
},
child4: {
source: false
}
},
parent3: {
child5: {
source: false
}
}
}
I want the outcome to be:
var afterFilter = {
parent1: {
child1: {
source: true
},
child2: {
source: true
}
},
parent2: {
child3: {
source: true
}
}
}
If you want a solution with a reuseable function, I suggest looking at this implementation.
const data = {parent1:{child1:{source:true},child2:{source:true}},parent2:{child3:{source:true},child4:{source:false}},parent3:{child5:{source:false}}}
function objectMapReduce (object, map, filter) {
// iterate key-value pairs of object
return Object.entries(object).reduce(
(accumulator, [key, value]) => {
// map each value in object
const result = map(value, key, object)
// filter each mapped value
return filter(result, key, object)
? Object.assign(accumulator, { [key]: result })
: accumulator
},
// initial value of accumulator
{}
)
}
const afterFilter = objectMapReduce(
data, // map-reduce each parent in data
parent => objectMapReduce(
parent, // map-reduce each child in parent
({ source}) => ({ source }), // copy each child
({ source }) => source // keep child if source is true
),
parent => Object.keys(parent).length > 0 // keep non-empty parent
)
console.log(afterFilter)
Instead of using Array methods, you can also try a simple for...of loop:
var data = {
parent1: {
child1: {
source: true
},
child2: {
source: true
}
},
parent2: {
child3: {
source: true
},
child4: {
source: false
}
},
parent3: {
child5: {
source: false
}
}
}
var afterFilter = {};
for (const [key, value] of Object.entries(data)){
for (const [k, v] of Object.entries(value)){
const { source } = v;
if (source !== true)
continue;
// If `afterFilter[key]` does not exist, init with {}
afterFilter[key] = afterFilter[key] || {};
afterFilter[key][k] = { source };
}
}
console.log(afterFilter)
Try this using Array.reduce and Object.entries , for each parent entry iterate through the children of the parent object filter it based on the source.
If the current child of the parent has the source as true then add it to the accumulator acc of the reduce else ignore it:
const data = {parent1:{child1:{source:true},child2:{source:true}},parent2:{child3:{source:true},child4:{source:false}},parent3:{child5:{source:false}}};
const res = Object.entries(data).reduce((acc, [key, value]) =>{
for(child in value){ //value is the child-object of the parent, iterating throgh all the key of the child and checking if the source is true for this key of the child
if(value[child].source){
acc[key] = {...acc[key], [child] : value[child]}; //using spread operator to preserve previous values
}
}
return acc;
}, {});
console.log(res);
If you find to find whose children is true and return that parent, maybe this is correct answer for you
const data = [
{ name: 'parent1', parent : { child: { source : true } } },
{ name: 'parent2', parent : { child: { source : true } } },
{ name: 'parent3', parent : { child: { source : false } } }
];
const newData = data.filter((e)=> e.parent.child.source === true);
console.log(newData);
This is my solution. Try this
var data = {
parent1: {
child1: {
source: true
},
child2: {
source: true
}
},
parent2: {
child3: {
source: true
},
child4: {
source: false
}
},
parent3: {
child5: {
source: false
}
}
}
var afterFilter = {}
for(var key in data){
for(var childkey in data[key]){
if(data[key][childkey].source){
if(afterFilter[key])
afterFilter[key][childkey] = data[key][childkey]
else
afterFilter[key] = {[childkey]: data[key][childkey]}
}
}
}
console.log(afterFilter);

In React/Redux reducer, how can I update a string in an nested array in an immutable way?

My store looks like this,
{
name: "john",
foo: {},
arr: [
{
id:101,
desc:'comment'
},
{
id:101,
desc:'comment2'
}
]
}
My textarea looks like this
<textarea
id={arr.id} //"101"
name={`tesc:`}
value={this.props.store.desc}
onChange={this.props.onChng}
/>
My action is
export const onChng = (desc) => ({
type: Constants.SET_DESC,
payload: {
desc
}
});
My reducer
case Constants.SET_DESC:
return update(state, {
store: {
streams: {
desc: { $set: action.payload.desc }
}
}
});
It works only if arry is an object, I had to make changes to the stream to an array and I am confused how I can update to an array, also how does get the right value from the store.
The following example taken from the redux documentation might help you in the use case how to update items in an array. For more on this you can read on here http://redux.js.org/docs/recipes/StructuringReducers.html
state structure is something like this
{
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true,
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}
and reducer code is like below
function updateObject(oldObject, newValues) {
// Encapsulate the idea of passing a new object as the first parameter
// to Object.assign to ensure we correctly copy data instead of mutating
return Object.assign({}, oldObject, newValues);
}
function updateItemInArray(array, itemId, updateItemCallback) {
const updatedItems = array.map(item => {
if(item.id !== itemId) {
// Since we only want to update one item, preserve all others as they are now
return item;
}
// Use the provided callback to create an updated item
const updatedItem = updateItemCallback(item);
return updatedItem;
});
return updatedItems;
}
function appReducer(state = initialState, action) {
switch(action.type) {
case 'EDIT_TODO' : {
const newTodos = updateItemInArray(state.todos, action.id, todo => {
return updateObject(todo, {text : action.text});
});
return updateObject(state, {todos : newTodos});
}
default : return state;
}
}
If you have to update an element in a array within your store you have to copy the array and clone the matching element to apply your changes.
So in the first step your action should contain either the already cloned (and changed) object or the id of the object and the properties to change.
Here is a rough example:
export class MyActions {
static readonly UPDATE_ITEM = 'My.Action.UPDATE_ITEM';
static updateItem(id: string, changedValues: any) {
return { type: MyActions.UPDATE_ITEM, payload: { id, changedValues } };
}
}
export const myReducer: Reducer<IAppState> = (state: IAppState = initialState, action: AnyAction): IAppState => {
switch (action.type) {
case MyActions.UPDATE_ITEM:
return { ...state, items: merge(state.items, action.payload) };
default:
return state;
}
}
const merge = (array, change) => {
// check if an item with the id already exists
const index = array.findIndex(item => item.id === change.id);
// copy the source array
array = [...array];
if(index >= 0) {
// clone and change the existing item
const existingItem = array[index];
array[index] = { ...existingItem, ...change.changedValues };
} else {
// add a new item to the array
array.push = { id: change.id, ...change.changedValues };
}
return array;
}
To update an array, I would use immutability helper and do something like this - to your reducer
let store = {"state" : {
"data": [{
"subset": [{
"id": 1
}, {
"id": 2
}]
}, {
"subset": [{
"id": 10
}, {
"id": 11
}, {
"id": 12
}]
}]
}}
case Constants.SET_DESC:
return update(store, {
"state" : {
"data": {
[action.indexToUpdate]: {
"subset": {
$set: action.payload.desc
}
}
}
}
})
});

Categories