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
Related
I have this kind of schema:
const schema = {
actions: {
ident: {
action: (v) => v,
path: 'some.path.key',
},
mul: {
action: (v) => v * 2,
path: 'some.other.path.key',
},
},
};
And a helper function, that takes object with keys present in schema actions, e.g:
const obj = {
ident: 1,
mul: 2,
}
const res = helper(schema, obj);
/* res */
{
some: {
path: {
key: 1,
},
other: {
path: {
key: 4,
}
}
},
}
And construct a new object with a function applied to the value.
Sometimes i need a behavior when both keys present in source object, e.g:
const schema2 = {
actions: {
ident: {
action: (v) => v,
path: 'some.path.key',
},
mul: {
action: (v) => v * 2,
path: 'some.other.path.key',
},
'mul:ident': {
action: (v1, v2) => v1/v2,
path: 'key',
}
},
};
In case like this, i need the result object to be:
const obj = {
ident: 1,
mul: 2,
}
const res = helper(schema, obj);
/* res */
{
key: 2 // 2/1 == 2
}
How can i implement such conditional logic in a good way?
I'd traverse your actions backwards, then delete the keys from the input, and skip the action if the keys are missing:
const input = { ...obj };
const output = {};
// you might want to sort the keys in the desired order first (e.g. by the number of parameters)
for(const [key, { action, path }] of Object.entries(schema.actions).reverse()) {
const keys = key.split(".");
// If one ov the values is missing, another action already consumed it
if(keys.some(key => !(key in input))
continue;
// consume all keys into values
const values = keys.map(key => {
const value = input[key];
delete input[key];
});
// TODO: assign path to output correctly
output[path] = action(...values);
}
I have a scenario
{
data:'',
skus: [
{ id: 1, ......}
{ id: 2, ......}
{ id: 3, ......}
]
api_first:'',
}
I have that schema and want to setState in somewhere skus on selected sku item and return changed item to original array
this.setState(produce(prevstate =>
prevstate.data.sku.obj="change"
))
this works for me
I'd recommend to use functional setState and map:
const updateSku = (skuId, data) => {
this.setState(prevState => ({
...prevState,
skus: prevState.skus.map(sku => {
if (sku.id === skuId) {
return {...sku, ...data}
} // else
return sku
})
}))
}
State immutability is important sometimes devs mutate states those are complex with multiple nested levels. You can always update state with simple javascript object update stratigy but I would suggest you to use immerjs. It reduces the code and makes it much more cleaner and easy to understand what is going to change. It helps a lot in redux reducers where a complex state needs to be updated with mutation
Here is example
https://immerjs.github.io/immer/docs/example-setstate
/**
* Classic React.setState with a deep merge
*/
onBirthDayClick1 = () => {
this.setState(prevState => ({
user: {
...prevState.user,
age: prevState.user.age + 1
}
}))
}
/**
* ...But, since setState accepts functions,
* we can just create a curried producer and further simplify!
*/
onBirthDayClick2 = () => {
this.setState(
produce(draft => {
draft.user.age += 1
})
)
}
Using immerjs, it will be
const updateSku = (skuId, data) => {
this.setState(produce(draft => {
const sku = draft.skus.find(s => s.id === skusId);
Object.assign(sku, data);
}));
}
What I have understood from your explanation is that when the SKU item gets changed you want to update the state Skus.
Here I've provided a solution for the same please try to relate with your example.
let's assume you have the following react component.
import React, { Component } from "react";
export class Sku extends Component {
constructor(props) {
super(props);
this.state = {
data: "",
skus: [
{ key: "key1", value: "value1" },
{ key: "key2", value: "value2" },
{ key: "key3", value: "value3" },
],
APIFirst: "",
};
}
handleSkuChange = (data) => {
this.setState(({ skus }) => {
const newSkus = skus.map(sku => (sku.key === data.key ? { ...sku, ...data } : sku));
return { skus: newSkus };
});
};
render() {
const { data, skus, APIFirst } = this.state;
const newSku = { key: 'key2', value: 'newSku' };
console.log("states =>", data, skus, APIFirst);
return (
<button type="button" onClick={() => this.handleSkuChange(newSku)}>'Change sku'</button>
);
}
}
The handleSkuChange function will work like it,
const skus = [
{ key: "key1", value: "value1" },
{ key: "key2", value: "value2" },
{ key: "key3", value: "value3" },
];
const handleSkuChange = (data) => (
skus.map(sku => (sku.key === data.key) ? { ...sku, value: "newValue" } : sku));
const newSku = { key: 'key2', value: 'newSku' };
console.log('old skus', skus);
console.log('new skus', handleSkuChange(newSku));
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 );
Is there an ES6 (and upwards) solution using destructuring and the spread operator to create a new object with a key and value deleted from the original object, when the key reference is dynamic, so:
const state = {
12344: {
url: 'http://some-url.com',
id: '12344'
},
12345: {
url: 'http://some-other-url.com',
id: '12345'
}
}
const idToDelete = 12344
const { [idToDelete], ...newState } = state // dynamic key
console.log('newState:', newState)
// desired newState would only have the key 12345 and its value
Unless it's my present Babel setup, I can't figure out the clean ES6 way of doing this (if it exists).
Many thanks in advance
when destructuring using dynamic id you need to set a var with the remove value : the doc about this
const state = {
12344: {
url: 'http://some-url.com',
id: '12344'
},
12345: {
url: 'http://some-other-url.com',
id: '12345'
}
}
const idToDelete = 12344
// the removed object will go to unusedVar
const { [idToDelete]: unusedVar, ...newState } = state // dynamic key
console.log('newState:', newState)
a better way if you don't need to keep the deleted object is to use the keyword delete
const state = {
12344: {
url: 'http://some-url.com',
id: '12344'
},
12345: {
url: 'http://some-other-url.com',
id: '12345'
}
}
const idToDelete = 12344
delete state[idToDelete]
console.log('newState:', state)
I don't think it's possible to cleanly achieve with ES6 destructuring. Since other answers include mutating the state, try this instead:
const state = {
12344: {
url: 'http://some-url.com',
id: '12344'
},
12345: {
url: 'http://some-other-url.com',
id: '12345'
}
}
const idToDelete = 12344
const newState = Object.assign({}, state);
delete newState[idToDelete];
console.log('newState:', newState)
console.log('old state:', state);
I am using Redux. In my reducer I'm trying to remove a property from an object like this:
const state = {
a: '1',
b: '2',
c: {
x: '42',
y: '43'
},
}
And I want to have something like this without having to mutate the original state:
const newState = {
a: '1',
b: '2',
c: {
x: '42',
},
}
I tried:
let newState = Object.assign({}, state);
delete newState.c.y
but for some reasons, it deletes the property from both states.
Could help me to do that?
How about using destructuring assignment syntax?
const original = {
foo: 'bar',
stack: 'overflow',
};
// If the name of the property to remove is constant
const { stack, ...withoutFirst } = original;
console.log(withoutFirst); // Will be { "foo": "bar" }
// If the name of the property to remove is from a variable
const key = 'stack'
const { [key]: value, ...withoutSecond } = original;
console.log(withoutSecond); // Will be { "foo": "bar" }
// To do a deep removal with property names from variables
const deep = {
foo: 'bar',
c: {
x: 1,
y: 2
}
};
const parentKey = 'c';
const childKey = 'y';
// Remove the 'c' element from original
const { [parentKey]: parentValue, ...noChild } = deep;
// Remove the 'y' from the 'c' element
const { [childKey]: removedValue, ...childWithout } = parentValue;
// Merge back together
const withoutThird = { ...noChild, [parentKey]: childWithout };
console.log(withoutThird); // Will be { "foo": "bar", "c": { "x": 1 } }
I find ES5 array methods like filter, map and reduce useful because they always return new arrays or objects. In this case I'd use Object.keys to iterate over the object, and Array#reduce to turn it back into an object.
return Object.assign({}, state, {
c: Object.keys(state.c).reduce((result, key) => {
if (key !== 'y') {
result[key] = state.c[key];
}
return result;
}, {})
});
You can use _.omit(object, [paths]) from lodash library
path can be nested for example: _.omit(object, ['key1.key2.key3'])
Just use ES6 object destructuring feature
const state = {
c: {
x: '42',
y: '43'
},
}
const { c: { y, ...c } } = state // generates a new 'c' without 'y'
console.log({...state, c }) // put the new c on a new state
That's because you are copying the value of state.c to the other object. And that value is a pointer to another javascript object. So, both of those pointers are pointing to the same object.
Try this:
let newState = Object.assign({}, state);
console.log(newState == state); // false
console.log(newState.c == state.c); // true
newState.c = Object.assign({}, state.c);
console.log(newState.c == state.c); // now it is false
delete newState.c.y;
You can also do a deep-copy of the object. See this question and you'll find what's best for you.
How about this:
function removeByKey (myObj, deleteKey) {
return Object.keys(myObj)
.filter(key => key !== deleteKey)
.reduce((result, current) => {
result[current] = myObj[current];
return result;
}, {});
}
It filters the key that should be deleted then builds a new object from the remaining keys and the initial object. The idea is stolen from Tyler McGinnes awesome reactjs program.
JSBin
function dissoc(key, obj) {
let copy = Object.assign({}, obj)
delete copy[key]
return copy
}
Also, if looking for a functional programming toolkit, look at Ramda.
As of 2019, another option is to use the Object.fromEntries method. It has reached stage 4.
const newC = Object.fromEntries(
Object.entries(state.c).filter(([key]) => key != 'y')
)
const newState = {...state, c: newC}
The nice thing about it is that it handles integer keys nicely.
You may use Immutability helper in order to unset an attribute, in your case:
import update from 'immutability-helper';
const updatedState = update(state, {
c: {
$unset: ['y']
}
});
It's easy with Immutable.js:
const newState = state.deleteIn(['c', 'y']);
description of deleteIn()
Here's an easy 1-liner you can use that allows you to partially apply the prop you want to remove. This makes it easy to pass to Array.map.
const removeProp = prop => ({ [prop]: _, ...rest }) => ({ ...rest })
Now you can use it like this:
const newArr = oldArr.map(removeProp('deleteMe'))
The issue you are having is that you are not deep cloning your initial state. So you have a shallow copy.
You could use spread operator
const newState = { ...state, c: { ...state.c } };
delete newState.c.y
Or following your same code
let newState = Object.assign({}, state, { c: Object.assign({}, state.c) });
delete newState.c.y
I normally use
Object.assign({}, existingState, {propToRemove: undefined})
I realise this isn't actually removing the property but for almost all purposes 1 its functionally equivalent. The syntax for this is much simpler than the alternatives which I feel is a pretty good tradeoff.
1 If you are using hasOwnProperty(), you will need to use the more complicated solution.
I use this pattern
const newState = Object.assign({}, state);
delete newState.show;
return newState;
but in book i saw another pattern
return Object.assign({}, state, { name: undefined } )
utility ;))
const removeObjectField = (obj, field) => {
// delete filter[selectName]; -> this mutates.
const { [field]: remove, ...rest } = obj;
return rest;
}
action type
const MY_Y_REMOVE = 'MY_Y_REMOVE';
action creator
const myYRemoveAction = (c, y) => {
const result = removeObjectField(c, y);
return dispatch =>
dispatch({
type: MY_Y_REMOVE,
payload: result
})
}
reducer
export default (state ={}, action) => {
switch (action.type) {
case myActions.MY_Y_REMOVE || :
return { ...state, c: action.payload };
default:
return state;
}
};
As hinted in some of the answers already, it's because you are trying to modify a nested state ie. one level deeper. A canonical solution would be to add a reducer on the x state level:
const state = {
a: '1',
b: '2',
c: {
x: '42',
y: '43'
},
}
Deeper level reducer
let newDeepState = Object.assign({}, state.c);
delete newDeepState.y;
Original level reducer
let newState = Object.assign({}, state, {c: newDeepState});
Use a combination of Object.assign, JSON.parse and JSON.stringify
const obj1 = { a: "a", b: "b" };
const obj2 = { c: "c", a: undefined };
const merged = Object.assign({}, obj1, obj2);
const sanitized = JSON.parse(JSON.stringify(merged));
console.log(sanitized); // -> { b: "b", c: "c" }