immerjs / immerjs/immer

Suggestion: Add notify method to allow signal based reactivity

Open
#1,189 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

proposal
Dominant language
JavaScript
Stars
29k
Forks
881
PR merge metrics
No merged PRs in 30d

Description

🚀 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];
}

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in createProxyProxy and inspect the ProxyState initialization, then trace objectTraps.set and originalObjectTrapsSet. Compare the proposed optional notification hook with the createDraft and finishDraft flow and the Svelte example; done means signal-based consumers can receive draft changes without affecting existing behavior when the hook is unset.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.