-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better support for stores with immutable objects #6678
Comments
Additionally, making We don't want to add |
By using ‘update’ myself, I loose the ability to pass member-expressions as binding values to sub components. would it be possible to make it somehow configurable? Otherwise, the “immutable” compiler option only goes half way… |
@derolf Given I don't think a strategy of "try |
Actually, I found a way to provide two-way bindings for stores that hold immutable using a "derived" store that offers an immerjs Draft for manipulation. /**
* Provide two-way bindings to an immutable value.
*/
export function draft<T>(store: Writable<T>): Writable<Draft<T>> {
const d = writable<Draft<T>>();
let lastValue: T;
store.subscribe((value) => {
lastValue = value;
d.set(createDraft(value));
});
d.subscribe((draft) => {
// using createDraft(draft) avoids making draft a dead proxy
const value = finishDraft(createDraft(draft)) as T;
if (value !== lastValue) {
store.set(value);
}
});
return d;
} Usage: <script>
const person = writable(
freeze(
{
age: 32,
},
true,
),
);
const personDraft = draft(person);
</script>
<input type="number" bind:value={$personDraft.age} /> |
Check out the tool It allows to implicitly apply immerjs Declare a mutable store:
Example:
is identical to
The actual use-case is to use directly bind to an input value. Will apply produce instead of direct modification:
Code: import produce from "immer";
import { derived, get } from "svelte/store";
import type { Writable } from "svelte/store";
export type Mutator<T> = { $: T } & { [P in keyof T]-?: Mutator<T[P]> };
/**
* Turns a store of a deeply nested object into a mutable tree.
*
* Assigning to `$` will use `produce` to create a mutated copy of the current value of `store` and assigns it to the `store`
*
* Example:
*
* ```
* mutable(people)["foo"].details.age.$ = 99;
* ```
*
* Identical to:
*
* ```
* people.update((people) => produce(people, (value) => value.details.age = 99));
* ```
*
* @param store
* @returns
*/
export function mutable<T>(store: Writable<T>): Writable<Mutator<T>> {
const ctx = new WeakMap();
const mut = derived(store, (value) => _mutable(ctx, store, value, []));
return {
subscribe: mut.subscribe,
set: (newValue) => {
if (get(mut) !== newValue) {
throw new Error("Should not be used");
}
},
update: () => {
throw new Error("Should not be used");
},
};
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function _mutable<T>(ctx: WeakMap<any, Mutator<any>>, store: Writable<T>, value: unknown, path: string[]): Mutator<T> {
if (typeof value === "object" && value !== null) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const mut = ctx.get(value);
if (mut) {
return mut as Mutator<T>;
}
}
const mut = new Proxy(
{},
{
get: (target, key: string) => {
if (key !== "$") {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
return _mutable(ctx, store, (value as any)[key], [...path, key]);
}
LOG.info(path.join("."));
return value;
},
set: (target, key: string, newValue) => {
if (key !== "$") {
return false;
}
if (path.length === 0) {
store.set(newValue);
return true;
}
store.update((value) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
produce(value, (draft: Record<string, any>) => {
for (let i = 0; i < path.length - 1; i++) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
draft = draft[path[i]];
}
if (newValue === undefined) {
delete draft[path[path.length - 1]];
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
draft[path[path.length - 1]] = newValue;
}
}),
);
return true;
},
},
) as Mutator<T>;
if (typeof value === "object" && value !== null) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
ctx.set(value, mut);
}
return mut;
} |
Describe the problem
$store.age = 33
is desugared into
set_store_value(store, $store.age = 33, $store);
However, if the store is storing an immutable object, it breaks the immutability.
Describe the proposed solution
Use
store.update
:Now, the store can use custom logic to track the changes and/or provide a proxy for updating.
use-case: use immerjs’
produce
inside a custom storeupdate
to clone objects when modifying them.Alternatives considered
None
Importance
would make my life easier
The text was updated successfully, but these errors were encountered: