Skip to content

Suggestion: Add notify method to allow signal based reactivity#1189

Description

@JannisKoeksel

馃殌 Feature Proposal

Add an optional notfy_ function to the ProxyState that defaults to null. So that the state in createProxyProxy looks like this:

const state: ProxyState = {
	type_: isArray ? ArchType.Array : (ArchType.Object as any),
	// Track which produce call this is associated with.
	scope_: parent ? parent.scope_ : getCurrentScope()!,
	// True for both shallow and deep changes.
	modified_: false,
	// Used during finalization.
	finalized_: false,
	// Track which properties have been assigned (true) or deleted (false).
	assigned_: {},
	// The parent draft state.
	parent_: parent,
	// The base state.
	base_: base,
	// The base proxy.
	draft_: null as any, // set below
	// The base copy with any updated values.
	copy_: null,
	// Called by the `produce` function.
	revoke_: null as any,
	isManual_: false,

	// new optional method for change notification; defaulting to null
	notify_: null
}

and then updating the object trap to wrap the set function:

 export const objectTraps: ProxyHandler<ProxyState> = {
	...
	set(state: ProxyObjectState, prop: string /* strictly not, but helps TS */, value) {
		const res = originalObjectTrapsSet(state,prop,value)
	
		if(state.notify_) state.notify_()
		return res 
	}

Motivation

The immer Proxy does not play nice with the svelte 5 rune proxy. Adding the notify_ method would allow adding reactivity to drafts.
I can't see any downsides in having the notify_ method as it has no noticeable performance impact when not used, is backwards compatible and opt in.

Can this be solved in user-land code?

I have not found a satisfying user land solution. Doesn't mean there isn't one

Example

A simple implementation that allows the default svelte 5 bind:value on inputs. Comments on svelte specific code; would work similar with other signal based systems.

const DRAFT_IMMER_PROXY = Symbol.for('og-immer-proxy');

function makeDraft<T extends object>(data: T) {
	const draft = createDraft(data);

	//update is a svelte internal signal to notify dependent values 
	const subscribe = createSubscriber((update) => {
		draft[DRAFT_STATE].notify_ = () => {
			update();
		};
	});

	const traps: ProxyHandler<Draft<T>> = {
		get(target, p, receiver) {
			if (p == DRAFT_IMMER_PROXY) {
				return draft;
			}
			
			// create svelte subscription
			subscribe();
			return Reflect.get(target, p, receiver);
		}
	};
	const proxy = new Proxy(draft, traps);

	return proxy;
}

function commit(draft: Draft<any>) {
	const immerDrat = draft[DRAFT_IMMER_PROXY];
	if (!immerDrat) return;
	let fwd;
	let bwd;
	const res = finishDraft(immerDrat, (p, u) => {
		fwd = p;
		bwd = u;
	});
	return [res, fwd, bwd];
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions