Skip to content

Commit

Permalink
Send patches to state subscribers (#363)
Browse files Browse the repository at this point in the history
Subscribers of the new BaseController will now also receive a patch for
each state change, in addition to a copy of the new state. Patches can
make it easier to efficiently serialize state updates.

This was done as part of the controller redesign (#337).
  • Loading branch information
Gudahtt authored and MajorLift committed Oct 11, 2023
1 parent a043c65 commit 3b2b294
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 9 deletions.
6 changes: 3 additions & 3 deletions src/BaseControllerV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ describe('BaseController', () => {
});

expect(listener1.callCount).toEqual(1);
expect(listener1.firstCall.args).toEqual([{ count: 1 }]);
expect(listener1.firstCall.args).toEqual([{ count: 1 }, [{ op: 'replace', path: [], value: { count: 1 } }]]);
expect(listener2.callCount).toEqual(1);
expect(listener2.firstCall.args).toEqual([{ count: 1 }]);
expect(listener2.firstCall.args).toEqual([{ count: 1 }, [{ op: 'replace', path: [], value: { count: 1 } }]]);
});

it('should inform a subscriber of each state change once even after multiple subscriptions', () => {
Expand All @@ -91,7 +91,7 @@ describe('BaseController', () => {
});

expect(listener1.callCount).toEqual(1);
expect(listener1.firstCall.args).toEqual([{ count: 1 }]);
expect(listener1.firstCall.args).toEqual([{ count: 1 }, [{ op: 'replace', path: [], value: { count: 1 } }]]);
});

it('should no longer inform a subscriber about state changes after unsubscribing', () => {
Expand Down
14 changes: 8 additions & 6 deletions src/BaseControllerV2.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { produce } from 'immer';
import { enablePatches, produceWithPatches } from 'immer';

// Imported separately because only the type is used
// eslint-disable-next-line no-duplicate-imports
import type { Draft } from 'immer';
import type { Draft, Patch } from 'immer';

enablePatches();

/**
* State change callbacks
*/
export type Listener<T> = (state: T) => void;
export type Listener<T> = (state: T, patches: Patch[]) => void;

/**
* Controller class that provides state management and subscriptions
Expand Down Expand Up @@ -67,10 +69,10 @@ export class BaseController<S extends Record<string, any>> {
* object. Return a new state object or mutate the draft to update state.
*/
protected update(callback: (state: Draft<S>) => void | S) {
const nextState = produce(this.internalState, callback) as S;
this.internalState = nextState;
const [nextState, patches] = produceWithPatches(this.internalState, callback);
this.internalState = nextState as S;
for (const listener of this.internalListeners) {
listener(nextState);
listener(nextState as S, patches);
}
}

Expand Down

0 comments on commit 3b2b294

Please sign in to comment.