Why immer.js doesn't allow setting dynamic properties on draft? - javascript

//I want my action to dispatch payload like
// {type:'update',payload:{'current.contact.mobile':'XXXXXXXXX'}}
//In reducer dynamically select the segment of state update needs to be applied to
//Below code doesn't work as expected though, draft always remains at same level
draft = dA.key.split('.').reduce((draft, k) => {
return draft[k]
}, draft);
//Or an ideal syntax may look like below line
draft['current.contact.mobile'] = dA.value;
//Code that works
draft['current']['contact']['mobile'] = dA.value;
I want my action to dispatch payload like
{type:'update',payload:{'current.contact.mobile':'XXXXXXXXX'}}
And in reducer dynamically select the segment of state that needs to be updated.
Is there something fundamentally wrong in doing this, I believe this could make life easier. Is there something that can done to achieve this ?

In your case, this code returns a primitive value like a string or number which is immutable.
draft = dA.key.split('.').reduce((draft, k) => {
return draft[k]
}, draft);
"Immer" is using Proxy to implement all this magic. The proxy could work only on objects for example Object, Array, Function etc.
so to fix your problem you can use code like this
import produce from "immer";
describe("Why immer.js doesn't allow setting dynamic properties on draft?", function() {
it("should allow set dynamic properties", function() {
const path = "foo.bar.zoo";
const state = { foo: { bar: { zoo: 1 } } };
const nextState = produce(state, draft => {
const vector = path.split(".");
const propName = vector.pop();
if (propName) {
draft = vector.reduce((it, prop) => it[prop], draft);
draft[propName] += 1;
}
});
expect(nextState.foo.bar.zoo).toEqual(state.foo.bar.zoo + 1);
});
});
In the code above, we get destination object and update the property of this object.
Some note about string and number.
Javascript has constructors for string and number which return objects not primitive values. But this is a very rare case when someone uses it explicitly.
Usually, we deal with it implicitly when writing something like this dA.key.split('.'). In this case, the interpreter would create a string object and call method "split" on it. Usually, this behavior is referred to as "Boxing"

Related

Redux how to update nested state object immutably without useless shallow copies?

Im using NGRX store on angular project.
This is the state type:
export interface TagsMap {
[key:number]: { // lets call this key - user id.
[key: number]: number // and this key - tag id.
}
}
So for example:
{5: {1: 1, 2: 2}, 6: {3: 3}}
User 5 has tags: 1,2, and user 6 has tag 3.
I have above 200k keys in this state, and would like to make the updates as efficient as possible.
The operation is to add a tag to all the users in the state.
I tried the best practice approach like so :
const addTagToAllUsers = (state, tagIdToAdd) => {
const userIds = Object.keys(state.userTags);
return userIds.reduce((acc, contactId, index) => {
return {
...acc,
[contactId]: {
...acc[contactId],
[tagIdToAdd]: tagIdToAdd
}
};
}, state.userTags);
};
But unfortunately this makes the browser crush when there are over 200k users and around 5 tags each.
I managed to make it work with this:
const addTagToAllUsers = (state, tagIdToAdd) => {
const stateUserTagsShallowCopy = {...state.userTags};
const userIds = Object.keys(stateUserTags);
for (let i = 0; i <= userIds.length - 1; i++) {
const currUserId = userIds[i];
stateUserTagsShallowCopy[currUserId] = {
...stateUserTagsShallowCopy[currUserId],
[tagIdToAdd]: tagIdToAdd
};
}
return stateUserTagsShallowCopy;
};
And the components are updated from the store nicely without any bugs as far as I checked.
But as written here: Redux website mentions :
The key to updating nested data is that every level of nesting must be copied and updated appropriately
Therefore I wonder if my solution is bad.
Questions:
I Believe I'm still shallow coping all levels in state, am i wrong ?
Is it a bad solution? if so what bugs may it produce that I might be missing ?
Why is it required to update sub nested level state in an immutable manner, if the store selector will still fire because the parent reference indeed changed. (Since it works with shallow checks on the top level.)
What is the best efficient solution ?
Regarding question 3, here is an example of the selector code :
import {createFeatureSelector, createSelector, select} from '#ngrx/store';//ngrx version 10.0.0
//The reducer
const reducers = {
userTags: (state, action) => {
//the reducer function..
}
}
//then on app module:
StoreModule.forRoot(reducers)
//The selector :
const stateToUserTags = createSelector(
createFeatureSelector('userTags'),
(userTags) => {
//this will execute whenever userTags state is updated, as long as it passes the shallow check comparison.
//hence the question why is it required to return a new reference to every nested level object of the state...
return userTags;
}
)
//this.store is NGRX: Store<State>
const tags$: Observable<any> = this.store.pipe(select(stateToUser))
//then in component I use it something like this:
<tagsList tags=[tags$ | async]>
</tagsList>
Your solution is perfectly fine.
The rule of thumb is that you cannot mutate an object/array stored in state.
In Your example, the only thing that You are mutating is the stateUserTagsShallowCopy object (it is not stored inside state since it is a shallow copy of state.userTags).
Sidenote: It is better to use for of here since you don't need to access the index
const addTagToAllUsers = (state, tagIdToAdd) => {
const stateUserTagsShallowCopy = {...state.userTags};
const userIds = Object.keys(stateUserTags);
for (let currUserId of userIds) {
stateUserTagsShallowCopy[currUserId] = {
...stateUserTagsShallowCopy[currUserId],
[tagIdToAdd]: tagIdToAdd
};
}
return stateUserTagsShallowCopy;
};
If you decide to use immer this will look like this
import produce from "immer";
const addTagToAllUsers = (state, tagIdToAdd) => {
const updatedStateUserTags = produce(state.userTags, draft => {
for (let draftTags of Object.values(draft)) {
tags[tagIdToAdd] = tagIdToAdd
}
})
return updatedStateUserTags
});
(this comes usually with performance cost). With immer you can sacrifice performance to gain readability
ad 3.
Why is it required to update sub nested level state in an immutable manner, if the store selector will still fire because the parent reference indeed changed. (Since it works with shallow checks on the top level.)
Every store change selectors recompute to see if the dependent component should re-render.
imagine that instead of immutable update of the user tags we decided to mutate tags inside user (state.userTags is a new object reference but we mutate (reuse) old entries objects state.userTags[userId])
const addTagToAllUsers = (state, tagIdToAdd) => {
const stateUserTagsShallowCopy = {...state.userTags};
const userIds = Object.keys(stateUserTags);
for (let currUserId of userIds) {
stateUserTagsShallowCopy[currUserId][tagIdToAdd] = tagIdToAdd;
}
return stateUserTagsShallowCopy;
};
In your case, you have a selector that takes out state.userTags.
It means that every time a state update happens nrgx will compare the previous result of the selector and the current one (prevUserTags === currUserTags by reference). In our case, we change state.userTags so the component that uses this selector will be refreshed with new userTags.
But imagine other selectors that instead of all userTags will select only one user tags. In our imaginary situation, we mutate directly userTags[someUserId] so the reference remains the same each time. The negative effect here is that subscribing component will be not refreshed (will not see an update after a tag is added).

Why does this getter method has been called several times in React?

I have a Store which will be provided to the component. In this Store file, there are several getter function. But I find only this getter function will be executed three times since this.rawMonthlyImpacts will be only changed once when the api get response from backend. I am so confused because other getter function in this file will be only executed once. During every execution, this.rawMonthlyImpacts is always same. Because this function is time-consuming, so I want to figure out why this happens. Hope you can give me some advice. Thanks!
get Impacts(){
const monthlyImpacts = new Map<string, Map<string, number>>();
if (this.rawMonthlyImpacts) {
this.rawMonthlyImpacts.forEach((impact) => {
if (impact.Impact > 0) {
const month = TimeConversion.fromTimestampToMonthString(impact.Month);
const tenantId = impact.TenantId;
const tenantImpact = impact.Impact;
if (!monthlyImpacts.has(month)) {
const tenantList = new Map<string, number>();
monthlyImpacts.set(month, tenantList.set(tenantId, tenantImpact));
} else {
const tenantWithImpactMap = monthlyImpacts.get(month);
if (!tenantWithImpactMap.has(tenantId)) {
tenantWithImpactMap.set(tenantId, tenantImpact);
} else {
tenantWithImpactMap.set(tenantId, tenantWithImpactMap.get(tenantId) + tenantImpact);
}
monthlyImpacts.set(month, tenantWithImpactMap);
}
}
});
}
return monthlyImpacts;
},
Update: I have find that there are other two functions use this.Impacts. If I remove these two functions, the getter function will only be executed only once. I think the getter function uses the cache to store data, so once the data is calculated for the first time, subsequent calls to the getter function should not be re-executed, only the value in the cache needs to be retrieved. So I am very confused about why this getter function will be executed 3 times.
getImpactedTenants(month: string): string[] {
return Array.from(this.Impacts.get(month).keys());
},
get overallMonthlyImpactedTenants(): Map<string, number> {
return new Map<string, number>(
Array.from(this.Impacts)?.map((monthEntries) => {
const month = monthEntries[0];
const impactedTenants = monthEntries[1].size;
return [month, impactedTenants];
})
);
}
Hard to tell what exactly is happening without more context, but remember that with a get function, every single time you reference that property (.Impacts in this case) the get function will be called.
Assuming that each impact stored in this.rawMonthlyImpacts which you loop through is an instance of the class with this getter, then as far as I'm aware, you are calling the get function each time you reference impact.Impacts, such as in the conditional:
if (impact.Impact > 0) {
I might be way off though; I'm unfamiliar with React and so my answer is based only on my experience with vanilla JS.

How to reduce amap Array.prototype.some() calls?

I have a service managing data fetched from a SocketIO API such as
export class DataService {
private data: SomeData[];
// ...
getData(): Observable<SomeData[]> {
// distinctUntilChanged was used to limit Observable to only emits when data changes
// but it does not seem to change much things...
return this.data.pipe(distinctUntilChanged());
}
// ...
}
and a component calling this service to do
this.banana$ = combineLatest([
someFnToRequestANetworkObject(),
DataService.getData()
]).pipe(
map(([network, data]) => network && data.some(_data=> _data.ip === network.ip))
);
The thing is that each time one of the Observable handled within combineLatest gets emitted, I get to call Array.prototype.some() function. Which I don't want to.
How could I optimize this code so that I don't call some too often ?
One thing to note about distinctUntilChanged() operator is it affects only the subscription. So the operators between the source observable and the subscription are still run. As workaround you could manually check if data has changed between emissions. Try the following
let oldData: any;
this.banana$ = combineLatest([
someFnToRequestANetworkObject(),
DataService.getData()
]).pipe(
map(([network, data]) => {
if (!oldData || oldData !== data) {
oldData = data;
return (network && data.some(_data=> _data.ip === network.ip));
}
return false; // <-- return what you wish when `data` hasn't changed
})
);
I think your hunch about distinctUntilChanged not working is correct.
By default, this operator uses an equality check to determine if two values are the same.
However, this will (in most cases) not work properly if the objects being compared aren't simple scalar values like numbers or booleans.
As it turns out, you can provide your own comparator function as the first argument to distinctUntilChanged.
This will be called on "successive" elements to determine if the new element is different from the most recent one.
The exact definition of this comparator function depends on what your SomeData class/interface looks like, along with what it means for an array of SomeData inhabitants to be the same as another one.
But, as an example, let's say that SomeData looks like this:
interface SomeData {
id: string;
name: string;
age: number;
}
and that SomeData inhabitants are the same if they have the same id.
Furthermore, let's suppose that two arrays of SomeDatas are the same if they contain exactly the same SomeData elements.
Then our comparator function might look like:
function eqSomeDataArrays(sds1: SomeData[], sds2: SomeData[]): boolean {
// Two arrays of `SomeData` inhabitants are the same if...
// ...they contain the same number of elements...
return sds1.length === sds2.length
// ...which are "element-wise", the same (i.e. have the same `id`)
&& sds1.every((sd1, i) => sd1.id === sds2[i].id)
}
To round it all out, your getData method would now look like:
getData(): Observable<SomeData[]> {
return this.data.pipe(distinctUntilChanged(eqSomeDataArrays));
}
in the pipe, after map call, you could use shareReplay(1)
this.banana$ = combineLatest([
someFnToRequestANetworkObject(),
DataService.getData()])
.pipe(
map(([network, data]) => network && data.some(_data=> _data.ip === network.ip)),
shareReplay(1)
);

What kind of JS data structure internals represent?

I am looking at celebrate.js.
Object internals
internals
{
DEFAULT_ERROR_ARGS: { celebrated: true },
DEFAULT_ERRORS_OPTS: { statusCode: 400 },
DEFAULT_CELEBRATE_OPTS: { mode: 'partial' }
}
Different actions are applied to this object later.
internals.validateSegment = (segment) => (spec, joiConfig) => {
const finalValidate = (req) => spec.validateAsync(req[segment], joiConfig);
finalValidate.segment = segment;
return finalValidate;
};
What do internals represent?
The internals object holds a set of default options (called segments here). They are accessed by their identifiers DEFAULT_ERROR_ARGS, DEFAULT_ERRORS_OPTS and DEFAULT_CELEBRATE_OPTS.
The segments are object themselves, that contain options (e.g. celebrated) and their default values (e.g. true).
All segments of internals can be passed to and validated by validateSegment(). Each property is defined by a key (here: spec) and a value (here: joiConfig). For each specification the function validateAsync() is called and the result is assigned to finalValidate.
The function validateSegment() can be called for individual segments. See function maybeValidateBody() in the code that you linked to, for example:
internals.maybeValidateBody = (segment) => {
const validateOne = internals.validateSegment(segment); <-- Called here!
return (spec, joiConfig) => {
...
In the above code block, the segment is passed to internals.validateSegment() and the return value is assigned to validateOne, for example.

How to nest Reselect selectors?

I have a selector that returns an array. The elements in the array themselves have derived data. I essentially need a recursive memoization selector that returns a derived array composed of derived elements.
my current attempt is:
export const selectEntitesWithAssetBuffers = createSelector(
[selectSceneEntities, getAssets],
(entities, loadedAssets) => {
return entities.map((entity) => {
entity.buffers = entity.assets.map((assetName) => {
return loadedAssets[assetName].arrayBuffer;
})
return entity;
})
}
)
My concerns here are anytime entities or loadedAssets change this will recompute the entire list. What I'm expecting to setup is something like a selectEntityWithBuffer that would get passed to the entities.map. Ideally, I want this to only recompute when an entity.assets array changes.
Reselect allows you to provide custom equality definitions to your selectors.
import { defaultMemoize, createSelectorCreator } from 'reselect'
const compareByAssets = (a, b) => {
return a.every((element, index) => {
return element.assets === b[index].assets
});
};
const createAssetsComparatorSelector = createSelectorCreator(
defaultMemoize,
compareByAssets
);
const selectSceneEntitiesByAssetsComparator = createAssetsComparatorSelector((state) => {
//however you normally get entities for the pre-existing selectors
});
Now you can use this new selectSceneEntitiesByAssetsComparator in place of the previous selectSceneEntities in the above code you provided and it will only re-run when the equality check in compareByAssets fails.
Feel free to further update that comparator function if a strict comparison of assets === assets doesn't suite your needs.
As a proof of concept, I'd try to provide loadedAssets object to the result function by bypassing reselect identity checks.
// Keep a private selector instance
let cachedSelector;
export const selectEntitesWithAssetBuffers = function(){
// loadedAssets should be recalculated on each call?
const loadedAssets = getAssets(arguments);
// create selector on first call
if(cachedSelector === undefined) {
cachedSelector = createSelector(
selectSceneEntities,
entities => {
return entities.map(entity => {
entity.buffers = entity.assets.map((assetName) => {
return loadedAssets[assetName].arrayBuffer;
})
return entity;
})
}
)
}
// Return selector result
return cachedSelector(arguments);
}
Getting deeper memoization than what you've got is kind of a tricky problem because Reselect doesn't really support passing arguments to selectors. If you're returning an array from your selector, and the input used to build that array has changed, it's sort of the intended behavior from Reselect that you will need to recompute. See the advice in the readme for dynamic arguments.

Categories

Resources