update deep nested array in redux - javascript

I've been looking around to find a solution or a guide to cover my problem..
I need to find the right subsubdoc by an ID i'm passing with the action, and then the right subsubsubdoc by an ID that i'm also passing with the action.
I'm fetching the object from an API and then putting in the state.
The object I have simular to this:
proj = {
id:"zf123ada123ad",
name:"examp",
subdoc:{
name:"subdoc examp",
subsubdoc:[{
id:"zcgsdf123zaar21",
subsubsubdoc:[{
id:"af2317bh123",
value: "heyhey" //this value I want to update
}]
}]
}
}
in my reducer i have something like this atm:
I know this don't work because I don't get the specific object in the array, but that's what i don't know how to do. I have the id of the subsubsubdoc that i want to change value of.
export function answerUpdate(state = [], action){
switch(action.type){
case 'ANSWER_UPDATE_FETCH_SUCCESS':
return {
...state,
proj: {
...state.proj,
subdoc: {
...state.proj.subdoc,
subsubdoc: {
...state.proj.subdoc.subsubdoc,
subsubsubdoc: {
...state.proj.subdoc.subsubdoc.subsubsubdoc,
value: "hoy"
}
}
}
}
default:
return state
}
}
What I would like is this but a working and acceptable code inside the reducer:
state.doc.subdoc.subsubdoc.where(x => x.id ==theInputId1)
.subsubsubdoc.where(
x => x.id == theInputId2).value = theInputValue
Very grateful for every answer!!

Assuming action.payload is your Id, you should do something like
case 'ANSWER_UPDATE_FETCH_SUCCESS': {
const index = state.proj.subdoc.subsubdoc.findIndex(({id}) => id === action.payload);
const updatedObject = {
...state.proj.subdoc.subsubdoc[index],
value: newValue
}
return {
...state,
proj: {
...state.proj,
subdoc: {
...state.proj.subdoc,
subsubdoc: [
...state.proj.subdoc.subsubdoc.slice(0, index),
updatedObject,
...state.proj.subdoc.subsubdoc.slice(index)
]
}
}
}
}
you can have additional checks for when id is not present, simplify by reducing the usage of state.proj.subdoc.subsubdoc by storing it in a variable.
It is advised not to have such deep nesting in redux store.

My first concern would be to get the data into a regular format. If you have no control over the API, try making the format yourself once you receive it. This will save you alot of complex code. Smart data structures and dumb program code usually performs better than louzy data structures and tons of complex code.
var project = [
{ "id": "zf123ada123ad",
"name": "examp",
"value": null,
"docs": [
{ "id": "zcgsdf123zaar21",
"name": "examp-sub",
"value": null,
"docs": [
{ "id": "af2317bh123",
"name": "examp-sub-sub",
"value": "heyhey",
"docs": []
}
]
}
]
}
];
// 1st option: Recusively search.
var search = function( id ) {
var search_level = function( result, next ) {
if ( result ) return result;
else {
if ( next.id === id ) return next;
else return next.docs.reduce( search_level, null );
}
};
return search_level;
};
var heyhey_search = project.reduce( search( "af2317bh123" ), null );
console.log( 'option 1:' );
console.log( heyhey_search );
// 2nd option: Make a hash table. This is preferred if you need to access records by id often.
var flatten_hash = function( result, next ) {
// Own.
result[ next.id ] = next;
// Nested docs.
return next.docs.reduce( flatten_hash, result );
};
var project_hash = project.reduce( flatten_hash, {} );
var heyhey_hash = project_hash[ "af2317bh123" ];
console.log( 'option 2:' );
console.log( heyhey_hash );
var state = [
{
"id": 1,
"docs": [
{
"id": 3,
"value": "old value"
}
]
},
{
"id": 2,
"docs": []
}
];
console.log( 'old state' );
console.log( JSON.stringify( state ) );
var object_3 = state[0].docs[0];
object_3.value = "new value";
console.log( 'new state' );
console.log( JSON.stringify( state ) );

Related

Why JavaScript push method is storing data as Vue JS observer instead of normal array

After I call the API I got this result:
[
{
"id_auto_mapping": 1,
"feed_field": "id",
"internal_field": "id"
},
{
"id_auto_mapping": 2,
"feed_field": "url",
"internal_field": "url"
},
{
"id_auto_mapping": 3,
"feed_field": "price",
"internal_field": "price"
}
]
and I loop through the result and save the keys to autoMappings array:
if( response.data.success ) {
if( response.data.auto_mappings.length > 0 ) {
response.data.auto_mappings.forEach( ( item ) => {
this.autoMappings.push(item.feed_field)
})
}
}
Now, If I do console like:
console.log( this.autoMappings )
I got this result:
Why is it saving as an observer instead of normal array?
How can I loop through the observer?
you can convert the observer into an array like this :
var parsedobj = JSON.parse(JSON.stringify(obj))
console.log(parsedobj)

React setState of for deeply nested value

I’ve got a very deeply nested object in my React state. The aim is to change a value from a child node. The path to what node should be updated is already solved, and I use helper variables to access this path within my setState.
Anyway, I really struggle to do setState within this nested beast. I abstracted this problem in a codepen:
https://codesandbox.io/s/dazzling-villani-ddci9
In this example I want to change the child’s changed property of the child having the id def1234.
As mentioned the path is given: Fixed Path values: Members, skills and variable Path values: Unique Key 1 (coming from const currentGroupKey and both Array position in the data coming from const path
This is my state object:
constructor(props) {
super(props);
this.state = {
group:
{
"Unique Key 1": {
"Members": [
{
"name": "Jack",
"id": "1234",
"skills": [
{
"name": "programming",
"id": "13371234",
"changed": "2019-08-28T19:25:46+02:00"
},
{
"name": "writing",
"id": "abc1234",
"changed": "2019-08-28T19:25:46+02:00"
}
]
},
{
"name": "Black",
"id": "5678",
"skills": [
{
"name": "programming",
"id": "14771234",
"changed": "2019-08-28T19:25:46+02:00"
},
{
"name": "writing",
"id": "def1234",
"changed": "2019-08-28T19:25:46+02:00"
}
]
}
]
}
}
};
}
handleClick = () => {
const currentGroupKey = 'Unique Key 1';
const path = [1, 1];
// full path: [currentGroupKey, 'Members', path[0], 'skills', path[1]]
// result in: { name: "writing", id: "def1234", changed: "2019-08-28T19:25:46+02:00" }
// so far my approach (not working) also its objects only should be [] for arrays
this.setState(prevState => ({
group: {
...prevState.group,
[currentGroupKey]: {
...prevState.group[currentGroupKey],
Members: {
...prevState.group[currentGroupKey].Members,
[path[0]]: {
...prevState.group[currentGroupKey].Members[path[0]],
skills: {
...prevState.group[currentGroupKey].Members[path[0]].skills,
[path[1]]: {
...prevState.group[currentGroupKey].Members[path[0]].skills[
path[1]
],
changed: 'just now',
},
},
},
},
},
},
}));
};
render() {
return (
<div>
<p>{this.state.group}</p>
<button onClick={this.handleClick}>Change Time</button>
</div>
);
}
I would appreciate any help. I’m in struggle for 2 days already :/
Before using new dependencies and having to learn them you could write a helper function to deal with updating deeply nested values.
I use the following helper:
//helper to safely get properties
// get({hi},['hi','doesNotExist'],defaultValue)
const get = (object, path, defaultValue) => {
const recur = (object, path) => {
if (object === undefined) {
return defaultValue;
}
if (path.length === 0) {
return object;
}
return recur(object[path[0]], path.slice(1));
};
return recur(object, path);
};
//returns new state passing get(state,statePath) to modifier
const reduceStatePath = (
state,
statePath,
modifier
) => {
const recur = (result, path) => {
const key = path[0];
if (path.length === 0) {
return modifier(get(state, statePath));
}
return Array.isArray(result)
? result.map((item, index) =>
index === Number(key)
? recur(item, path.slice(1))
: item
)
: {
...result,
[key]: recur(result[key], path.slice(1)),
};
};
const newState = recur(state, statePath);
return get(state, statePath) === get(newState, statePath)
? state
: newState;
};
//use example
const state = {
one: [
{ two: 22 },
{
three: {
four: 22,
},
},
],
};
const newState = reduceStatePath(
state,
//pass state.one[1],three.four to modifier function
['one', 1, 'three', 'four'],
//gets state.one[1].three.four and sets it in the
//new state with the return value
i => i + 1 // add one to state.one[0].three.four
);
console.log('new state', newState.one[1].three.four);
console.log('old state', state.one[1].three.four);
console.log(
'other keys are same:',
state.one[0] === newState.one[0]
);
If you need to update a deeply nested property inside of your state, you could use something like the set function from lodash, for example:
import set from 'lodash/set'
// ...
handleClick = () => {
const currentGroupKey = 'Unique Key';
const path = [1, 1];
let nextState = {...this.state}
// as rightly pointed by #HMR in the comments,
// use an array instead of string interpolation
// for a safer approach
set(
nextState,
["group", currentGroupKey, "Members", path[0], "skills", path[1], "changed"],
"just now"
);
this.setState(nextState)
}
This does the trick, but since set mutates the original object, make sure to make a copy with the object spread technique.
Also, in your CodeSandbox example, you set the group property inside of your state to a string. Make sure you take that JSON string and construct a proper JavaScript object with it so that you can use it in your state.
constructor(props) {
super(props)
this.setState = { group: JSON.parse(myState) }
}
Here's a working example:
CodeSandbox

How to access other object sibling's value?

I'm just wondering if it's possible to refer to self (object) value inside the object sibling like below?
[
{
"name": "Zulh",
"name_uppercase": uppercase(self.name) // expects ZULH
},
{
"name": "John",
"name_uppercase": uppercase(self.name) // expects JOHN
}
]
Note:
Code for uppercase is omitted for brevity. In my real code, it's doing synchronous complex stuff and is not actually simple string case manipulation like that.
Using a GETTER
If you want to keep it dynamic and make it work even if you change the name property, you can use a GETTER to do this kind of thing:
const names = [
{
"name": "John",
get name_uppercase() {
return this.name.toUpperCase();
}
}
]
console.log(names[0].name_uppercase)
GETTER for multiple objects
You don't have to write this for every property manually! Use .forEach:
const names = [
{
"name": "John"
},
{
"name": "Mike"
}
]
names.forEach(object => {
Object.defineProperty(object, 'nameUppercase', {
get: function() { return this.name.toUpperCase() }
});
});
console.log(names[0].nameUppercase)
console.log(names[1].nameUppercase)
Using a class and a GETTER
Or as #Rajesh pointed out you can use a class instead:
class Person {
constructor(name) {
this.name = name;
}
get nameUpperCase() {
return this.name.toUpperCase();
}
}
const names = [ new Person("John"), new Person("Mike")];
console.log(names[0].nameUpperCase);
console.log(names[1].nameUpperCase);
You can't reference an object during initialization when using object literal syntax.. Inshort, that's not possible what you expect above
Well, you can use map and add additional/modified properties to you object like
data.map(o=> ({name: o.name, upper_case : o.name.toUpperCase()}))
var data = [
{
"name": "Zulh"
},
{
"name": "John"
}
];
var x = data.map(o=> ({name: o.name, upper_case : o.name.toUpperCase()}))
console.log(x)
You can use Array.forEach and update the objects in Array
var data = [{"name": "Zulh"},{"name": "John"}];
data.forEach(o=> o.upper_case = o.name.toUpperCase());
console.log(data);
Why not create a function that transforms your incoming array? A way to do it could be like this:
const value = [
{
"name": "Zulh"
},
{
"name": "John"
}
];
const transform = ( array, propertyToUpdate, propertyToCreate, transformation ) => {
return array.map( item => ({ ...item, [propertyToCreate]: transformation( item[propertyToUpdate] ) }) );
};
console.log( transform( value, 'name', 'name_uppercase', ( item ) => item.toUpperCase() ) );
You can't do this with the object literal syntax, since it's 'this' property will not be set at that time. For example, if you'd run your code in the browser, 'this' would refer to the window object.
So you'll either have to use one of the other answers or go for a 'class':
var uppercase = function( str ) {
return str.toUpperCase();
};
var Person = function( name ) {
this.name = name;
this.name_uppercase = uppercase( this.name );
};
var persons = [
new Person( 'zuhi' ),
new Person( 'john' )
];
console.log( persons );
Same can be written in ES6 class syntax.
I would suggest 2 approaches:
If you DO NOT want to change your initial array ( which is recommended ), use map which returns a new array with changed values ( calls a function for every array item ) .
See below
let arr = [
{
"name": "Zulh",
},
{
"name": "John",
}
];
const newArr = arr.map((x)=>{
x.name_uppercase = (x.name).toUpperCase()
return x
})
console.log(newArr)
If you don't mind changing your initial array, you can use forEach. Keep in mind that unlike map, forEach changes your array and so it doesn't return anything.
let arr = [
{
"name": "Zulh",
},
{
"name": "John",
}
];
arr.forEach((x)=>{
x.name_uppercase = (x.name).toUpperCase()
})
console.log(arr)
So it all depends if you want to change your current array or not
How about using a getter method?
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
The get syntax binds an object property to a function that will be
called when that property is looked up.
foo = [
{
"name": "Zulh",
get name_uppercase () {
return (this.name).toUpperCase();
}
},
{
"name": "John",
get name_uppercase () {
return (this.name).toUpperCase();
}
}
]
console.log(foo[1].name_uppercase); //returns JOHN
Hope it helps :)

Parsing a value from JSON response that keeps changing order

Im trying to parse JSON response from my home automation system in javscript.
The response is
available here
That is only a small part of the response, also, the order of keys changes on every reboot for reasons i do not know, su using the the number index wont really work
i need to be able to store the state value of sensor.out, sensor.in, sensor.door into variables for tasker on andorid
i tried to select using the entity.id, but for some reason the code never finished ( i believe i just didnt know what i was doing)
With ES6 you can use the Array#find method:
response.find(o => o.entity_id == 'sensor.out').state
See snippet:
var response = [ { "attributes":{ "friendly_name":"door" }, "entity_id":"sensor.door", "last_changed":"2016-12-31T11:15:59.395808+00:00", "last_updated":"2016-12-31T11:15:59.395808+00:00", "state":"closed" }, { "attributes":{ "friendly_name":"In", "unit_of_measurement":"\u00b0C" }, "entity_id":"sensor.in", "last_changed":"2016-12-31T11:20:02.179821+00:00", "last_updated":"2016-12-31T11:20:02.179821+00:00", "state":"20.7" }, { "attributes":{ "changed_by":null, "code_format":".+", "friendly_name":"panel" }, "entity_id":"alarm_control_panel.panel", "last_changed":"2016-12-31T11:14:56.471966+00:00", "last_updated":"2016-12-31T11:14:56.471966+00:00", "state":"disarmed" }, { "attributes":{ "friendly_name":"Out", "unit_of_measurement":"\u00b0C" }, "entity_id":"sensor.out", "last_changed":"2016-12-31T11:14:58.452345+00:00", "last_updated":"2016-12-31T11:14:58.452345+00:00", "state":"7.1" }];
var state = response.find(o => o.entity_id == 'sensor.out').state;
console.log('sensor.out state is', state);
Alternatively, you could convert the response to an object with the entity id values as keys, so you can access it like response['session.out'].state:
response = Object.assign({}, ...response.map( o => ({[o.entity_id]: o}) ));
See snippet:
var response = [ { "attributes":{ "friendly_name":"door" }, "entity_id":"sensor.door", "last_changed":"2016-12-31T11:15:59.395808+00:00", "last_updated":"2016-12-31T11:15:59.395808+00:00", "state":"closed" }, { "attributes":{ "friendly_name":"In", "unit_of_measurement":"\u00b0C" }, "entity_id":"sensor.in", "last_changed":"2016-12-31T11:20:02.179821+00:00", "last_updated":"2016-12-31T11:20:02.179821+00:00", "state":"20.7" }, { "attributes":{ "changed_by":null, "code_format":".+", "friendly_name":"panel" }, "entity_id":"alarm_control_panel.panel", "last_changed":"2016-12-31T11:14:56.471966+00:00", "last_updated":"2016-12-31T11:14:56.471966+00:00", "state":"disarmed" }, { "attributes":{ "friendly_name":"Out", "unit_of_measurement":"\u00b0C" }, "entity_id":"sensor.out", "last_changed":"2016-12-31T11:14:58.452345+00:00", "last_updated":"2016-12-31T11:14:58.452345+00:00", "state":"7.1" }];
response = Object.assign({}, ...response.map( o => ({[o.entity_id]: o}) ));
console.log('sensor.out state is', response['sensor.out'].state);
If you're trying to uses indexes to select properties from objects, you shouldn't be, unless there is a very specific reason to do so.
Fortunately that's fine, you don't need to know the order. I took two of the objects from your JSON array, scrambled up the properties, and wrote a function that returns any object that contains the key/val you specify.
Your question is a little hard to follow, but I think this will give you the idea.
<script type="text/javascript">
let arr = [
{
"attributes":{
"friendly_name":"door"
},
"entity_id":"sensor.frontdoor",
"last_changed":"2016-12-31T11:15:59.395808+00:00",
"last_updated":"2016-12-31T11:15:59.395808+00:00",
"state":"closed"
},
{
"last_changed":"2016-12-31T11:15:59.395808+00:00",
"state":"closed",
"attributes":{
"friendly_name":"door"
},
"entity_id":"sensor.backdoor",
"last_updated":"2016-12-31T11:15:59.395808+00:00"
}
];
function findKey ( theKey, theVal ) {
let reduced = arr.filter ( d => {
return d [ theKey ] === theVal;
});
return reduced;
}
let targets = findKey ( 'entity_id', 'sensor.backdoor' );
targets.forEach ( d => {
// This check is a little naive, but should give you the idea
if ( 'state' in d ) {
console.log ( d.state );
}
} );
</script>
What everyone says is correct: The order of keys in the response doesn't matter. Use the (string) key, not a numerical index.
var arr = [
{
"entity_id":"sensor.door",
"state":"closed"
}]; // other attributes chopped out for brevity
var entity_id_interested_in = 'sensor.door';
var state = '[entity_id not found in response]';
for (var i = 0; i < arr.length; i++) {
console.log(arr[i].entity_id + ' state:' + arr[i].state);
if (arr[i].entity_id == entity_id_interested_in)
{
state = arr[i].state;
break;
}
}
console.log (state);

Advanced Array.prototype.filter with Javascript

I have an Javascript object like so...
var strategies = [{
"strategy": {
"category": "war"
}
}, {
"strategy": {
"category": "farming"
}
}]
I then have an array that indicates which results I'd like back. It can be any of the following: [] OR ["war"] ["farming"] OR ["war", "farming"].
If we have the [], I want to return no results. But if ["war", "farming"] I want to return both of the results above.
How do I accomplish this with Array.prototype.filter? I saw this post, but couldn't reason through it.
strategies.filter((strategy) =>
????
)
Thanks for your help.
You can just check the value with indexOf:
var categories = ['war', 'farming'];
var filtered = strategies.filter((obj) => {
return categories.indexOf(obj.strategy.category) > -1;
});
Your object, strategy was a wrapped object, so my first line was setting it to its inner strategy and then filter as needed.
var strategies = [{
"strategy": {
"category": "war"
}
}, {
"strategy": {
"category": "farming"
}
}]
var b = ["war", "farming"];
strategies.filter(function(strategy){
strategy = strategy.strategy;
for(var i in b){
if (b[i] == strategy["category"]) {
return true;
}
}
return false;
});
Tests the input array to see if it's empty as per your requirements:
function filterObj(arr) {
return !arr.length ? arr :
strategies.filter((el) => arr.indexOf(el.strategy.category) > -1);
}
filterObj(['war', 'farming'])
DEMO

Categories

Resources