Skip to content

Commit

Permalink
fix(types): Minor typescript fixes.
Browse files Browse the repository at this point in the history
  • Loading branch information
jwalton committed Oct 14, 2022
1 parent 883c177 commit 7db1a1a
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 9 deletions.
20 changes: 11 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { createDraft, Draft, finishDraft, Immutable, produce } from 'immer';
import { useEffect, useRef, useState } from 'react';

type Objectish = { [key: string]: any } | unknown[];

/**
* Keep track of immutable state in a controller.
*/
export class ImmutableModelStore<S> {
export class ImmutableModelStore<S extends Objectish> {
/** The current state for this store. */
public current: Immutable<S>;

// If we are in the middle of an update, this is the draft that is being updated.
private _isUpdating: Draft<S> | undefined;
private _isUpdating: Draft<Immutable<S>> | undefined;

// A list of subscribers to this store.
private _subscribers: ((newValue: Immutable<S>) => void)[] = [];

/**
* Create a new ImmutableModelStore.
*/
constructor(initialState: S) {
this.current = produce(initialState, () => void 0) as Immutable<S>;
constructor(initialState: Immutable<S>) {
this.current = produce(initialState, () => void 0);
}

public get updating(): boolean {
Expand All @@ -33,22 +35,22 @@ export class ImmutableModelStore<S> {
* be applied when `fn` returns. Recursive/nested calls to `update` will be
* handled correctly.
*/
public update(fn: (state: Draft<S>) => void): void {
public update(fn: (state: Draft<Immutable<S>>) => void): void {
if (this._isUpdating) {
fn(this._isUpdating);
} else {
const draft = (this._isUpdating = createDraft(this.current) as Draft<S>);
const draft = (this._isUpdating = createDraft(this.current));

try {
// Apply the update...
const voidResult = fn(draft) as any;
const voidResult = fn(draft);

// Paranoid check for async functions...
if (typeof voidResult === 'object' && 'then' in voidResult) {
throw new Error('Updates must be synchronous');
}

const newState = finishDraft(draft) as unknown as Immutable<S>;
const newState = finishDraft(draft);

this._isUpdating = undefined;
if (newState !== this.current) {
Expand Down Expand Up @@ -111,7 +113,7 @@ function useLatest<T>(thing: T): [React.MutableRefObject<T>, boolean] {
* will be compared using `===`. If this is provided, this function will be
* used instead.
*/
export function useControllerState<S, R = Immutable<S>>(
export function useControllerState<S extends Objectish, R = Immutable<S>>(
store: ImmutableModelStore<S>,
selector?: (state: Immutable<S>) => R,
isEqual?: (oldState: R, newState: R) => boolean
Expand Down
19 changes: 19 additions & 0 deletions test/ImmutableModelStoreTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,23 @@ describe('ImmutableModelStore', () => {
// We should not be in the middle of a re-rentrant update.
expect(store.updating).to.be.false;
});

it('should accept various types of objects', () => {
new ImmutableModelStore({ obj: true });
new ImmutableModelStore([1, 2, 3]);
});

it('should allow explicit types', () => {
interface Person {
name: string;
age: number;
}

const store = new ImmutableModelStore<Person>({ name: 'Jason', age: 30 });
store.update((state: Person) => {
state.name = 'Oriana';
});

expect(store.current).to.eql({ name: 'Oriana', age: 30 });
});
});

0 comments on commit 7db1a1a

Please sign in to comment.