Dynamically set multiple object properties using variable - javascript

I wanna set multiple object property with the same value.
const SHOW_PAYMENT_DIALOG = 'SHOW_PAYMENT_DIALOG';
const SHOW_BUSINESS_DIALOG = 'SHOW_BUSINESS_DIALOG';
const handler = (state, payload) => {
return {
...state,
data: payload
};
};
const object = {
[SHOW_BUSINESS_DIALOG]: handler,
[SHOW_PAYMENT_DIALOG]: handler,
};
As above example, I have to manually assign handler for 2 property SHOW_BUSINESS_DIALOG & SHOW_PAYMENT_DIALOG
Is there anyway to set it quickly on the fly by js api but no need to introduce a new function to handle that like
const object = {
[SHOW_BUSINESS_DIALOG, SHOW_PAYMENT_DIALOG]: handler,
};

You could use Object.fromEntries() with .map() if you're okay with having the same reference to handler for each value... (the snippet console output shows how the handler method is the same reference for each value, that's why it looks a little strange):
const SHOW_PAYMENT_DIALOG = 'SHOW_PAYMENT_DIALOG';
const SHOW_BUSINESS_DIALOG = 'SHOW_BUSINESS_DIALOG';
const handler = (state, payload) => {
return {
...state,
data: payload
};
};
const keys = [SHOW_PAYMENT_DIALOG, SHOW_BUSINESS_DIALOG];
const object = Object.fromEntries(keys.map(k => [k, handler]));
console.log(object);
Please note that .fromEntries() is currently in draft mode, however, I think a generic if statement accompanied with a Set (using .has()) would be better for this case than using an object:
const SHOW_PAYMENT_DIALOG = 'SHOW_PAYMENT_DIALOG';
const SHOW_BUSINESS_DIALOG = 'SHOW_BUSINESS_DIALOG';
const handler = (state, payload) => {
return {
...state,
data: payload
};
};
const get_handler = key => {
const keys = new Set([SHOW_PAYMENT_DIALOG, SHOW_BUSINESS_DIALOG]);
if(keys.has(key))
return handler;
}
console.log(get_handler(SHOW_BUSINESS_DIALOG)); // hanlder
console.log(get_handler("foo")); // undefined

You can create a generic function and pass an array of key and loop through keyArr and place value for each key
const SHOW_PAYMENT_DIALOG = 'SHOW_PAYMENT_DIALOG';
const SHOW_BUSINESS_DIALOG = 'SHOW_BUSINESS_DIALOG';
let value = "some value"
const object = { someKey: 'value'};
let dynamicSetValues = (keyArr, value) => {
keyArr.forEach(key => {
object[key] = value
})
}
dynamicSetValues(['SHOW_PAYMENT_DIALOG','SHOW_BUSINESS_DIALOG'], value)
console.log(object)
Note:- this mutates original object, if you want immutability you can make a copy of object and place value on desired keys and return a new object every time from function

Related

Problem pushing a value inside an array located in an object key

Suppose I have the following function:
const createMenu = () => {
const obj = {
consumption: [],
};
return obj;
};
This is a function that, when called, returns the object
{ consumption: [] }
What I am trying to do is create a key inside that object that is a function that, when called with a string parameter, it pushes the string into the array inside the key 'consumption';
Here's my attempt:
const createMenu = () => {
const obj = {
consumption: [],
};
let order = (item) => {obj.consumption.push(item); };
obj.order = order;
return obj;
};
The expected result is that, when calling that function inside the object with a string parameter,like this:
createMenu().order('pizza');
when I run:
console.log(createMenu().consumption);
my result is:
['pizza']
but it is not working. I would appreciate if anyone could help me with this.
const createMenu = () => {
const obj = {
consumption: [],
};
let order = (item) => {
obj.consumption.push(item);
};
obj.order = order;
return obj;
};
createMenu().order('pizza');
console.log(createMenu().consumption);
You creating two instance of createMenu,
Your likely wanting to create 1
const menu = createMenu()
Also if you want to chain the functions, you will want to return the obj again inside order.
Below is an example..
const createMenu = () => {
const obj = {
consumption: [],
};
let order = (item) => {
obj.consumption.push(item);
return obj;
};
obj.order = order;
return obj;
};
const menu = createMenu().order('pizza');
console.log(menu.consumption);
You have to store the object created by createMenu() to a variable then perform operations to that variable. The update below should work.
In your code, you've created a new object when called the createMenu() function at the console.log. Which is not what you are wanting.
const createMenu = () => {
const obj = {
consumption: [],
};
let order = (item) => {
obj.consumption.push(item);
};
obj.order = order;
return obj;
};
const menu = createMenu();
menu.order('pizza');
menu.order('burger');
console.log(menu.consumption); // ["pizza", "burger"]
.as-console-wrapper{min-height: 100% !important; top: 0}
You may want to consider a class for this. Set up the array in the constructor, add a method to update the array when a new menu item is introduced, and have a final method that returns your desired object.
class CreateMenu {
constructor() {
this.consumption = [];
};
orderItem(item) {
this.consumption.push(item);
return this;
}
getList() {
return { consumption: this.consumption };
}
};
const menu = new CreateMenu();
const order = menu
.orderItem('pizza')
.orderItem('cheese sticks')
.getList();
console.log(order);

Can't call object method on previous state in setState hook inside subscribe body

Trying to update an objects state using the useState hook does not work inside a RxJS subject subscribe body, but does outside it. (annotated with the comments in the second code section)
The error I receive is: TypeError: eventMap.addEvent is not a function
My goal is to call addEvent from EventMap with the event from the backpageMessageSubject$, and later on passing the returned array from getNthEvent method to a child component.
class EventMap {
eventArrays;
constructor() {
this.eventArrays = new Map();
}
addEvent = (id, data) => {
this.eventArrays.has(id)
? this.eventArrays.get(id).push(data)
: this.eventArrays.set(id, [data]);
return this.eventArrays;
}
getNthEvent = (n) => {
const result = [];
for (let key of this.eventArrays.keys()) {
result.push(this.eventArrays.get(key)[n]);
}
return result;
}
}
export default function MarbleDiagram(props) {
const [eventMap, setEventMap] = useState(new EventMap());
// Stream of events for this observable.
const event$ = backpageMessageSubject$.pipe(
filter(message => message.type === 'event'),
map(message => message.event),
filter(event => event.observable === props.observable)
);
useEffect(() => {
const eventSubscription = event$.subscribe(event => {
setEventMap(eventMap => eventMap.addEvent(1, 1)); // This does trigger error.
});
setEventMap(eventMap => eventMap.addEvent(1, 1)); // This does not trigger error.
return () => {
eventSubscription.unsubscribe();
};
}, []);
}
subject origin:
export const backpageMessageSubject$ = new Subject();
Once you've done setEventMap(eventMap => eventMap.addEvent(1, 1)), what you have in state for the event map is the return value of eventMap.addEvent, which isn't an EventMap instance. So later when you try to use it, it fails because what you're using (a Map) doesn't have addEvent.
Since items in state must not be directly modified, you'll need to update EventMap so that it returns a new instance of the map with the added event, rather than mutating the existing map, along these lines:
class EventMap {
eventArrays;
constructor(eventArrays = new Map()) { // Note accepting a parameter
this.eventArrays = eventArrays;
}
addEvent = (id, data) => {
// Copy the current map, reusing the same arrays
const eventArrays = new Map(this.eventArrays);
// Get the current array for this `id`
const current = eventArrays.get(id);
if (!current) {
// We don't have it, create a new one in the new map
eventArrays.set(id, [data]);
} else {
// We have it, copy its contents to a new array and
// add the new data to the end, storing it in the
// new map
eventArrays.set(id, [...current, data]);
}
// Create a new EventMap with those arrays and return it
return new EventMap(eventArrays);
}
getNthEvent = (n) => {
const result = [];
for (let key of this.eventArrays.keys()) {
result.push(this.eventArrays.get(key)[n]);
}
return result;
}
}

Best way to call a function before every function in an object?

I'm creating an interface and i need to run a check function before most methods, repeating the code over and over doesn't seem great. Is there a way I can run a function before a function?
Example of my current code
const uploadObject = async (object) => {
if (!ipfs) noProvider()
const buffer = objectToIpfsBuffer(object)
return ipfs.add(buffer)
}
const uploadString = async (string) => {
if (!ipfs) noProvider()
const buffer = stringToIpfsBuffer(string)
return ipfs.add(buffer)
}
const uploadBuffer = async (buffer) => {
if (!ipfs) noProvider()
return ipfs.add(buffer)
}
...
module.exports = {
uploadObject,
uploadString,
uploadBuffer,
...
}
The function I wish to run before is if (!ipfs) noProvider()
I see no issue with handling this the way you are; however, another approach to "hook" a property accessor is to use a Javascript Proxy.
The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
When initializing a Proxy, you'll need to provide two function inputs:
target: A target object (can be any sort of object, including a native array, a function or even another proxy) to wrap with Proxy.
handler: An object which is a placeholder object which contains traps for Proxy. All traps are optional. If a trap has not been defined, the default behavior is to forward the operation to the target.
Here's an example:
const handler = {
get: function(target, prop, receiver) {
console.log('A value has been accessed');
return Reflect.get(...arguments);
}
}
const state = {
id: 1,
name: 'Foo Bar'
}
const proxiedState = new Proxy(state, handler);
console.log(proxiedState.name);
I would probably just do it inline like you are, but to add another tool to your toolbelt: you could create a higher order function which takes in a function and the produces a new function that will do the check, and then do the work.
const checkIpfs = fxn => {
return (...args) => {
if (!ipfs) noProvider();
return fxn(...args);
}
}
const uploadObject = checkIpfs(async (object) => {
const buffer = objectToIpfsBuffer(object)
return ipfs.add(buffer);
});
const uploadString = checkIpfs(async (string) => {
const buffer = stringToIpfsBuffer(string)
return ipfs.add(buffer)
})
const uploadBuffer = checkIpfs(async (buffer) => {
return ipfs.add(buffer)
})
You could use the Proxy Object that intercepts internal operation of other object.
var newObject = new Proxy(yourObject, {
get(target, prop, receiver){
if(['uploadObject', 'uploadString','uploadBuffer'].includes(prop) && type(target[prop]) == typeof(Function)) {
if (!ipfs) noProvider()
return Reflect.get(target, prop, receiver);
}
},
});
newObject.uploadObject();

How to add "fallback" value to optional function argument in flow?

I have following function utalising flow types
push = (pathname: string, data?: Object) => {
const history = [...this.state.history, pathname];
this.setState({
history,
pathname,
data
});
};
In normal javascript I would be able to do something like (pathname, data = null) where null would be used for data if it wasn't provided, but I can't figure out syntax for this when using flow types.
let push = (pathname: string, data?: Object = {a: 1}) => {
// ...
};
In addition, you can also use default values with destructuring together:
type Arg = { prop: number };
const func = ({ prop = 1 }: Arg) => prop;

Create an Observable object from a list of Observables

I'm still wrapping my head around RxJS and there is this pattern I keep running into and that I would like to find a more elegant way to write.
Implementing the model part of a Model-View-Intent pattern component, I have a function that takes actions as input an returns a single state$ Observable as output.
function model(actions) {
const firstProperty$ =
const anotherProperty$ = …
// Better way to write this?
const state$ = Rx.Observable.combineLatest(
firstProperty$, anotherProperty$,
(firstProperty, anotherProperty) => ({
firstProperty, anotherProperty
})
);
return state$;
}
So my model method computes a bunch of observables, every one of them emit items that represents a part of the state of my application. That is fine.
But how to I cleanly combine them into a single one observable that emits states, each state being a single object whose keys are the initial observable names?
I borrowed this pattern from https://github.com/cyclejs/todomvc-cycle :
function model(initialState$, actions){
const mod$ = modifications(actions)
return initialState$
.concat(mod$)
.scan( (state, mod) => mod(state))
.share()
}
function modifications(actions){
const firstMod$ = actions.anAction$.map(anAction => (
state => ({ ...state,
firstProperty: anAction.something
})
const secondMod$ = actions.otherAction$.map(otherAction => (
state => ({ ...state,
firstProperty: otherAction.something,
secondProperty: aComputation(otherAction)
})
return Rx.Observable.merge([firstMod$, secondMod$ ]).share()
}
In the main function :
const initialState$ = Rx.Observable.from({})
const actions = intent(DOM)
const state$ = model(initialState$, actions).share()
Using help from CHadrien, here is a working solution.
const prop1$ = Rx.Observable.of('foo');
const prop2$ = Rx.Observable.of('bar');
const prop3$ = Rx.Observable.of('baz');
const prop4$ = Rx.Observable.of('foobar');
function combineObservables(objectOfObservables) {
const keys = Object.keys(objectOfObservables);
const observables = keys.map(key => objectOfObservables[key]);
const combined$ = Rx.Observable.combineLatest(
observables, (...values) => {
var obj = {};
for (let i = 0 ; i < keys.length ; i++) {
obj[keys[i]] = values[i];
}
return obj;
}
);
return combined$;
}
combineObservables({prop1$, prop2$, prop3$, prop4$}).subscribe(x => console.log(x));
And the result:
[object Object] {
prop1$: "foo",
prop2$: "bar",
prop3$: "baz",
prop4$: "foobar"
}

Categories