How to update react state properly - javascript

I am new to react and I want to ask what's the best way to update state, I have some code. I know the code below is not correct as it's setting the state directly.
handlexxx = foo => {
const foos = [...this.state.foos];
const index = foos.indexOf(foo);
foos[index].bar = !foo.bar;
this.setState({ foos });
};
Those two code below which one is better? can some one explain me please!
handlexxx = foo => {
const foos = [...this.state.foos];
const index = foos.indexOf(foo);
foos[index] = { ...foo };
foos[index].bar = !foo.bar;
this.setState({ foos });
};
handlexxx = foo => {
const foos = [...this.state.foos];
const index = foos.indexOf(foo);
foos[index] = { ...foos[index] };
foos[index].bar = !foos[index].bar;
this.setState({ foos });
};
My account got blocked by some down votes questions, the funny thing is I have to re-edit them, even though I already have the accepted answer.I do not understand what's the point to do this.I am so frustrated by this stackoverflow system.
Now, I basically can do nothing but keep editing my questions, and they have all been answered. This is ridiculous !!!

You should use Array.prototype.map() method, like this:
handlexxx = foo => {
const foos = this.state.foos.map(f => {
if(foo.id === f.id) return {...f, bar: !f.bar}; // assume that the element has an identifier id
return f;
})
this.setState({ foos });
};
For short, using ternary operator instead of if-else statement
handlexxx = foo => {
const foos = this.state.foos.map(f => foo.id === f.id ? {...f, bar: !f.bar} : f
this.setState({ foos });
};

One classic way to avoid mutations even for complex nested objects is to use JSON.parse(JSON.stringify(COMPLICATED_OBJECT)). This will return a representation of your object that has no reference to the original object, so you can mutate the copy without affecting the original:
var foos = [
{ id: 1, bar: false },
{ id: 2, bar: false },
{ id: 3, bar: false },
]
var foo = foos[0];
var _foos = JSON.parse(JSON.stringify(foos)).map(f => {
if (f.id === foo.id) f.bar = !foo.bar;
return f;
});
If you run this, you'll see foos is unchanged but _foos is updated
At the end of the day, you might want to think about which solution you find most readable, and which solution other developers on your team might find most readable. If you have to return to this code in 3 years, you'll want to be able to read the code off the page without any head scratching.

Since your foo object has an id property it is better to use .map method and mix this with spread syntax or Object.assign, then return the related elements as #Nguyễn Thanh Tú explained. But, if you want to do this with indexes here are the examples.
You can use findIndex, not indexOf. Since indexOf just look for a value, findIndex accepts a function. Find the index of the item, map the array, then if index matches do the change if it does not match return the unrelated item.
state = {
foos: [
{ id: 1, bar: true },
{ id: 2, bar: false },
{ id: 3, bar: false },
],
};
const foo = { id: 2, bar: false };
const handleState = foo => {
const index = state.foos.findIndex(el => el.id === foo.id );
const newFoos = state.foos.map( ( foo, i ) => {
if ( i !== index ) { return foo };
return { ...foo, bar: !foo.bar };
})
console.log( newFoos );
}
handleState(foo);
Second one. Here, we are using Object.assign in a tricky way. Instead of mapping the array we use Object.assign and change the item using its index.
state = {
foos: [
{ id: 1, bar: true },
{ id: 2, bar: false },
{ id: 3, bar: false },
],
};
const foo = { id: 2, bar: false };
const handleState2 = foo => {
const index = state.foos.findIndex(el => el.id === foo.id );
const newFoos = Object.assign( [], state.foos, { [index]: { ...foo, bar: !foo.bar } });
console.log( newFoos );
}
handleState2(foo);
Third one. Without an index, with only .map and using directly the id property. We don't need to find an index here, we are just checking with the id property to find the right item.
state = {
foos: [
{ id: 1, bar: true },
{ id: 2, bar: false },
{ id: 3, bar: false },
],
};
const foo = { id: 2, bar: false };
const handleState3 = foo => {
const newFoos = state.foos.map( el => {
if ( el.id !== foo.id ) { return el };
return { ...el, bar: !el.bar };
})
console.log( newFoos );
}
handleState3( foo );

Related

How to successfully "merge" two sets of plain-objects with getters?

Below I have two sets of plain-objects, and within each there is a getter. I'd love to find a way to merge these two objects. However when I merge them I'd like them to still be getters. I do not want the values within side getters to be resolved.
const love = {
get cats() {
return 'meow';
},
};
const hate = {
get dogs() {
return 'woof';
},
};
console.log({...love, ...hate}); // { cats: 'meow', dogs: 'woof' }
console.log(Object.assign(love, hate)); // { cats: [Getter], dogs: 'woof' }
Use defineProperties to put the properties on the object, spreading into it values from Object.getOwnPropertyDescriptors:
const love = {
get cats() {
return 'meow';
},
};
const hate = {
get dogs() {
return 'woof';
},
};
const result = Object.defineProperties({}, {
...Object.getOwnPropertyDescriptors(love),
...Object.getOwnPropertyDescriptors(hate),
});
console.log(Object.getOwnPropertyDescriptor(result, 'dogs'));
Made this little nugget from #CertainPerformances answer:
function mergeGetters<A, B>(a: A, b: B): A & B {
const result = Object.defineProperties(
{},
{
...Object.getOwnPropertyDescriptors(a),
...Object.getOwnPropertyDescriptors(b),
}
);
return result;
}

Aggregate same key values into an array and avoid undefined

I am trying to aggregate the same key values into an array by value.
so for example I have an array of objects, like so
const data = [{foo: true},{foo: false},{bar: true},{buzz: false}]
when they get aggregated the array transforms into
[
foo: {true: [{foo: true}], false: [{foo: false}]},
bar: {true: [{bar: true}]},
buzz: {false: [{buzz: false}]}
]
the array entries is the original object.
Now I know the keys that I want to group by..
they are foo, bar, buzz and fizz.
But fizz is not part of the original array, so the return is undefined, like so
[
foo: {true:[{foo: true}], false: [{foo: false}]},
bar: {true: [{bar: true}]},
buzz: {false: A[{buzz: false}]}
fizz: {undefined: [{foo: true},{foo: false},{bar: true},{buzz: false}]}
],
how do I reduce the original array without including the fizz value that is undefined?
code here:
let v = [];
let types = ['foo', 'bar', 'buzz', 'fizz' ]
for (let x = 0; x < types.length; x++) {
let data = data.reduce((acc, i) => {
if (!acc[i[types[x]]]) {
acc[i[types[x]]] = [i]
}
else if (Array.isArray(acc[i[types[x]]])) {
acc[i[types[x]]].push(i);
}
else if (typeof acc[i[types[x]]] === 'object') {
acc[i[types[x]]] = [acc[i[types[x]]]]
acc[i[types[x]]].push(i)
}
return acc;
}, {})
v.push({ [types[x]]: data });
}
return v;
You were close, you just need to check if the property you were adding was undefined before adding. You can also check if the reduced object has any properties before adding to the result object.
Note that this may not be the most efficient way of doing it, but sometimes it's better to understand the code than it is to have highly efficient code.
const data = [{
foo: true
}, {
foo: false
}, {
bar: true
}, {
buzz: false
}];
let v = [];
let types = ['foo', 'bar', 'buzz', 'fizz']
for (let x = 0; x < types.length; x++) {
let reduced = data.reduce((acc, i) => {
// /* Added this type check */
if (!acc[i[types[x]]] && typeof i[types[x]] !== 'undefined') {
acc[i[types[x]]] = [i]
} else if (Array.isArray(acc[i[types[x]]])) {
acc[i[types[x]]].push(i);
} else if (typeof acc[i[types[x]]] === 'object') {
acc[i[types[x]]] = [acc[i[types[x]]]]
acc[i[types[x]]].push(i)
}
return acc;
}, {});
// Doesn't add a property for the type if there are no data
if (Object.keys(reduced).length) {
v.push({
[types[x]]: reduced
});
}
}
console.log(v);
Have a look at how Array.prototype.reduce works. It might be the right method to build your approach upon.
A generic way of solving the OP's problem was to iterate the provided data array. For each item one would extract its key and value. In case the item's key is listed (included) in another provided types array, one would continue creating a new data structure and collecting the currently processed item within the latter.
One does not want to iterate the types array for it will cause a unnecessarily complex lookup for the data items, each time a type item is going to be processed.
Thus a generically working (better code reuse) reduce method might be the best solution to the OP's problem ...
const sampleDataList = [
{ foo: true },
{ foo: false },
{ bar: true },
{ baz: false },
{ buzz: false },
{ baz: false },
{ bar: true }
];
// foo: {true: [{foo: true}], false: [{foo: false}]},
// bar: {true: [{bar: true}]},
// buzz: {false: [{buzz: false}]}
function collectItemIntoInclusiveKeyValueGroup(collector, item) {
const { inclusiveKeyList, index } = collector;
const firstItemEntry = Object.entries(item)[0];
const key = firstItemEntry[0];
const isProceedCollecting = ( // proceed with collecting ...
//
!Array.isArray(inclusiveKeyList) // - either for no given list
|| inclusiveKeyList.includes(key) // - or if item key is listed.
);
if (isProceedCollecting) {
let keyGroup = index[key]; // access the group identified
if (!keyGroup) { // by an item's key, ... or ...
// ...create it in case ...
keyGroup = index[key] = {}; // ...it did not yet exist.
}
const valueLabel = String(firstItemEntry[1]); // item value as key.
let valueGroupList = keyGroup[valueLabel]; // acces the group list
if (!valueGroupList) { // identified by an item's
// value, ...or create it in
valueGroupList = keyGroup[valueLabel] = []; // case it did not yet exist.
}
// push original reference into a grouped
// key value list, as required by the OP.
valueGroupList.push(item);
}
return collector;
}
console.log(
"'foo', 'bar', 'buzz' and 'fizz' only :",
sampleDataList.reduce(collectItemIntoInclusiveKeyValueGroup, {
inclusiveKeyList: ['foo', 'bar', 'buzz', 'fizz'],
index: {}
}).index
);
console.log(
"'foo', 'bar' and 'baz' only :",
sampleDataList.reduce(collectItemIntoInclusiveKeyValueGroup, {
inclusiveKeyList: ['foo', 'bar', 'baz'],
index: {}
}).index
);
console.log(
"all available keys :",
sampleDataList.reduce(collectItemIntoInclusiveKeyValueGroup, {
index: {}
}).index
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Try something like:
const data = [{foo: true},{foo: false},{bar: true},{buzz: false}];
let v = [];
let types = ['foo', 'bar', 'buzz', 'fizz' ];
for (let x = 0; x < types.length; x++) {
let filteredlist = data.filter(function (d) {
return Object.keys(d)[0] == types[x];
});
let isTrue = 0;
let isFalse = 0;
if (filteredlist.length > 0) {
for (let i = 0; i < filteredlist.length; i++) {
let trueOrfalse = eval("filteredlist[i]." + types[x]);
if (trueOrfalse) {
isTrue++;
} else {
isFalse++;
}
}
v.push(types[x], {true: isTrue, false: isFalse});
}
}
console.log(v);
Assuming you only want to count the number of each key (e.g. true or false) you can use the following code.
I've written this as a function named 'aggregate' so that it can be called multiple times with different arguments.
const initialData = [{foo: true},{foo: true},{foo: false},{bar: true},{buzz: false}];
const types = ['foo', 'bar', 'buzz', 'fizz'];
const aggregate = (data, types) => {
const result = {};
data.forEach(item => {
// Extract key & value from object
// Note: use index 0 because each object in your example only has a single key
const [key, value] = Object.entries(item)[0];
// Check if result already contains this key
if (result[key]) {
if (result[key][value]) {
// If value already exists, append one
result[key][value]++;
} else {
// Create new key and instantiate with value 1
result[key][value] = 1;
}
} else {
// If result doesn't contain key, instantiate with value 1
result[key] = { [value]: 1 };
}
});
return result;
};
console.log(aggregate(initialData, types));
This will output the following (note I've added another {foo: true} to your initialData array for testing).
The output should also be an object (not array) so that each key directly relates to its corresponding value, as opposed to an Array which will simply place the value as the next item in the Array (without explicitly linking the two).
{
foo: { true: 2, false: 1 },
bar: { true: 1 },
buzz: { false: 1 }
}

Destructuring first value of an array of objects in reduce method

Let's say you an array of objects, where you're reducing a property into one result separated by dashes, e.g:
const array = [
{ foo: "foo" },
{ foo: "foo" },
]
Should become the string:
foo-foo
If you're using the reduce method, you might do something like this:
const array = [ { foo: "foo" }, { foo: "foo" }, { foo: "foo" } ];
const result = array.reduce( ( accumulator, { foo } ) => {
return accumulator + "-" + foo;
} );
console.log( result );
However, the problem is that the default initial value (the first element of the array) is the entire first object, naturally resulting in [object Object]-foo-foo.
Question is: is there a simple way to, for example destructure, the initial value?
You can bypass the issue by, for example, using an if-statement specifically checking whether the accumulator is currently an object:
const array = [ { foo: "foo" }, { foo: "foo" }, { foo: "foo" } ];
const result = array.reduce( ( accumulator, { foo } ) => {
if ( accumulator.hasOwnProperty( "foo" ) ) {
return accumulator.foo + "-" + foo;
}
return accumulator + "-" + foo;
} );
console.log( result );
However, I am interested in a simpler/prettier way of doing this, using less "arbitrary-looking" code, perhaps a way to do this using the actual initialValue argument of reduce.
Note: I am not looking for an answer to an actual real problem I am facing now (and thus not looking for alternative solutions such as for let/of loops or filtering the array), I am asking for the sake of learning more about the reduce method in these types of situations for future reference.
You could set the initial value to an empty string, and use a ternary to check if the string is empty, if true, then return only the foo, otherwise, return the accumulator, a dash, and foo:
const array = [ { foo: "foo" }, { foo: "foo" }, { foo: "foo" } ];
const result = array.reduce((a, {foo}) => a ? `${a}-${foo}` : foo, '');
console.log(result);
We can be sure that on the initial loop that the ternary will return only foo as '' evaluates to false.
You could map and join the items.
var array = [{ foo: "foo" }, { foo: "foo" }, { foo: "foo" }],
result = array
.map(({ foo }) => foo)
.join('-');
console.log(result);
A reduce approach with a check if a dash is necessary.
var array = [{ foo: "foo" }, { foo: "foo" }, { foo: "foo" }],
result = array.reduce((r, { foo }) => r + (r && '-') + foo, '');
console.log(result);

How to watch object changes with rxjs 5

I would like to watch over an object, so all the subscribers will be informed for any changes of it.
I saw it already been asked before,
yet the answer is irrelevant since RXjs verion 5 do not include the ofObjectChanges in it's API anymore.
I've looked at some "hacks" like creating an observer which return a function:
let myObservable = new Observable((observer) => {
return (data) => {
observer.next(data)
}
})
//...
myObservable.subscribe()('someData')
However, I'm sure there is more elegant way of doing it.
Any Ideas?
The ES6 way of observing an object is with Proxies. You create a Proxy that wraps the original object and do your work on it. You can use it to create something similar to Observable.ofObjectChanges. Here a partial implementation (only set. You'd need to implement the other traps):
Observable.ofProxyChanges = (target) => {
let subject = new Subject
let proxy = new Proxy(target, {
set(target, key, val) {
let oldValue = target[key]
target[key] = val
subject.next({
type: oldValue === undefined ? "add" : "change",
object: target,
name: key,
oldValue: oldValue
})
}
})
return [proxy, subject.asObservable()]
}
let [obj, objChange$] = Observable.ofProxyChanges({})
objChange$.subscribe(console.log)
obj.bar = 1 // logs { type: "add", name: "bar", object: { bar: 1 } }
obj.foo = 2 // logs { type: "add", name: "foo", object: { bar: 1, foo: 2 } }
obj.foo = 3 // logs { type: "change", name: "foo", object: { bar: 1, foo: 3 }, oldValue: 2 }
I would suggest using something similar to redux approach, when changes to the object can be made in predefined way:
function factory(reducerByType, initialState) {
const action$ = new Rx.Subject();
const state$ = action$
.startWith(initialState)
.scan((state, action) => {
if (reducerByType.hasOwnProperty(action.type)) {
return reducerByType[action.type](state, action);
}
return state;
})
.distinctUntilChanged();
return {
action$,
state$,
dispatch: action => action$.next(action)
}
}
const {state$, dispatch} = factory({
ADD: (state, action) => state + action.number,
SUBTRACT: (state, action) => state - action.number,
}, 0);
state$.subscribe(val => console.log(val));
dispatch({
type: 'ADD',
number: 10,
});
dispatch({
type: 'SUBTRACT',
number: 15,
});
dispatch({
type: 'SUBTRACT',
number: 0,
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.0/Rx.js"></script>
You need to use Behavior Subject . https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/subjects/behaviorsubject.md

RxJS: Asynchronously mutate tree

I have a sequence of objects that I need to asynchronously modify by adding a property to each object:
[{ id: 1 }, { id: 2 }] => [{ id: 1, foo: 'bar' }, { id: 2, foo: 'bar' }]
The synchronous equivalent of this would be:
var xs = [{ id: 1 }, { id: 2 }];
// Warning: mutation!
xs.forEach(function (x) {
x.foo = 'bar';
});
var newXs = xs;
However, in my case I need to append the foo property asynchronously. I would like the end value to be a sequence of objects with the foo property added.
I came up with the following code to solve this problem. In this example I'm just adding a property to each object with a value of bar.
var xs = Rx.Observable.fromArray([{ id: 1 }, { id: 2 }]);
var propertyValues = xs
// Warning: mutation!
.flatMap(function (x) {
return Rx.Observable.return('bar');
});
var newXs =
.zip(propertyValues, function (x, propertyValue) {
// Append the property here
x.foo = propertyValue;
return x;
})
.toArray();
newXs.subscribe(function (y) { console.log(y); });
Is this the best way to solve my problem, or does Rx provide a better means for asynchronously mutating objects in a sequence? I'm looking for a cleaner solution because I have a deep tree that I need to mutate, and this code quickly becomes unweidly:
var xs = Rx.Observable.fromArray([{ id: 1, blocks: [ {} ] }, { id: 2, blocks: [ {} ] } ]);
var propertyValues = xs
// Warning: mutation!
.flatMap(function (x) {
return Rx.Observable.fromArray(x.blocks)
.flatMap(function (block) {
var blockS = Rx.Observable.return(block);
var propertyValues = blockS.flatMap(function (block) {
return Rx.Observable.return('bar');
});
return blockS.zip(propertyValues, function (block, propertyValue) {
block.foo = propertyValue;
return block;
});
})
.toArray();
});
xs
.zip(propertyValues, function (x, propertyValue) {
// Rewrite the property here
x.blocks = propertyValue;
return x;
})
.toArray()
.subscribe(function (newXs) { console.log(newXs); });
Perhaps I shouldn't be performing this mutation in the first place?
Is there a reason you need to create two separate Observables: one for the list you're updating and one for the resulting value?
If you simply perform a .map() over your original list, you should be able to asynchronously update the list and subscribe to the result:
// This is the function that generates the new property value
function getBlocks(x) { ... }
const updatedList$ = Rx.Observable.fromArray(originalList)
// What we're essentially doing here is scheduling work
// to be completed for each item
.map(x => Object.assign({}, x, { blocks: getBlocks(x)}))
.toArray();
// Finally we can wait for our updatedList$ observable to emit
// the modified list
updatedList$.subscribe(list => console.log(list));
To abstract this functionality, I created a helper function that will explicitly schedule work to occur for each item using setTimeout:
function asyncMap(xs, fn) {
return Rx.Observable.fromArray(xs)
.flatMap(x => {
return new Rx.Observable.create(observer => {
setTimeout(() => {
observer.onNext(fn(x));
observer.completed();
}, 0);
});
})
.toArray();
}
You can use this function to schedule work to be completed for each item:
function updateItem(x) {
return Object.assign({}, x, { blocks: getBlocks(x) }
}
var updatedList$ = asyncMap(originalList, updateItem);
updateList$.subscribe(newList => console.log(newList));

Categories