Skip to content

Commit

Permalink
Merge pull request #2 from scorbettUM/atom-updates
Browse files Browse the repository at this point in the history
Atom updates
  • Loading branch information
adalundhe authored Feb 25, 2024
2 parents 61e673d + 46eb176 commit 069daed
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 75 deletions.
21 changes: 14 additions & 7 deletions examples/example.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { create, compare, atom } from "../src/react";
import { create, compare, atom, useAtom } from "../src/react";

interface Store {
boop: {
Expand Down Expand Up @@ -35,11 +35,18 @@ const { beep } = useMyCustomStore(
}),
);

const useMyAtom = atom({
value: beep,
update: (value: string) => value.toLowerCase(),
});
const useMyAtom = atom<typeof beep>(
beep,
(set) => async (next: string) => set(next),
);

const [value, update] = useMyAtom((state) => state);

const { value, update } = useMyAtom();
console.log(update(value + "Test"));

console.log(value, update);
const test = (set) => async (next: string) => set(next);

const [myValue, setState] = useAtom(
beep,
(set) => async (next: string) => set(next),
);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "delta-state",
"version": "1.0.10",
"version": "1.1.0",
"description": "A modern version of the Delta state manager - written for TS and with the use of React Hooks.",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
25 changes: 7 additions & 18 deletions src/atom.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,22 @@
import { AtomStore } from "./types.ts";

class Atom<T extends AtomStore<T>, K extends T["value"]> {
private value;
private updateFn?: (value: K) => K;
private subscribers: Set<() => void>;

constructor({ value, update }: { value: K; update?: (value: K) => K }) {
class Atom<T> {
value: T;
subscribers: Set<() => void>;
constructor(value: T) {
this.value = value;
this.updateFn = update;
this.subscribers = new Set();
}
subscribe(callback: () => void) {
this.subscribers.add(callback);
return () => this.subscribers.delete(callback);
}

update(value: K) {
this.value = this.updateFn ? this.updateFn(value) : value;
this.subscribers.forEach((callback) => callback());
return this.value;
getSubscribers() {
return this.subscribers;
}

getValue() {
getState() {
return this.value;
}

setUpdateFn(update: (value: K) => K) {
this.updateFn = update;
}
}

export { Atom };
97 changes: 60 additions & 37 deletions src/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useMemo, useSyncExternalStore } from "react";
import useSyncExports from "use-sync-external-store/shim/with-selector.js";
import { Atom } from "./atom.ts";
import { Store } from "./store.ts";
import { AtomStore, StoreApi, Listener } from "./types.ts";
import { Listener, StoreApi } from "./types.ts";

const { useSyncExternalStoreWithSelector } = useSyncExports;

Expand All @@ -25,7 +25,7 @@ const createImpl = <T extends StoreApi<T>>(store: Store<T>, init: T) => {
(callback: Listener) => store.subscribe(callback),
() => store.getStore(),
() => init,
(state: T) => selector(state),
selector,
comparator
? (a: U, b: U) =>
comparator({
Expand All @@ -39,52 +39,75 @@ const createImpl = <T extends StoreApi<T>>(store: Store<T>, init: T) => {
return useCreatedStore;
};

export const useAtom = <T extends AtomStore<T>, K extends T["value"]>(
export const useAtom = <T>(
atom: T,
update?: (value: K) => K,
update: (set: (next: T) => T) => (next: T) => T | Promise<T>,
) => {
const atomStore = useMemo(
() =>
new Atom<T, K>({
value: atom.value as K,
update: update,
}),
[atom, update],
);
const atomStore = useMemo(() => new Atom<T>(atom), [atom]);

return {
value: useSyncExternalStore(
(callback) => atomStore.subscribe(callback),
() => atomStore.getValue(),
() => atom.value,
) as K,
update: (value: K) => atomStore.update(value),
const set = (next: T) => {
atomStore.value = next;
atomStore.subscribers.forEach((callback) => callback());
return next;
};

const setUpdate = update(set);

return [
useSyncExternalStore(
(callback) => atomStore.subscribe(callback),
() => atomStore.getState(),
() => atomStore.getState(),
),
setUpdate,
] as [T, typeof setUpdate];
};

const createAtomImpl = <T extends AtomStore<T>, K extends T["value"]>(
atom: Atom<T, K>,
init: K,
const createAtomImpl = <T>(
atomStore: Atom<T>,
update: (next: T) => T | Promise<T>,
) => {
return () => ({
value: useSyncExternalStore(
(callback) => atom.subscribe(callback),
() => atom.getValue(),
() => init,
) as K,
update: (value: K) => atom.update(value),
});
const init = atomStore.getState();

const useCreatedStore = <U>(
selector: (value: T) => U,
comparator?: ({ next, prev }: { next: U; prev: U }) => boolean,
) => {
return [
useSyncExternalStoreWithSelector(
(callback: Listener) => atomStore.subscribe(callback),
() => atomStore.getState(),
() => init,
selector,
comparator
? (a: U, b: U) =>
comparator({
next: a,
prev: b,
})
: undefined,
),
update,
] as [U, typeof update];
};

return useCreatedStore;
};

export const atom = <T extends AtomStore<T>>(
atom: T & { update: (value: T["value"]) => T["value"] },
export const atom = <T>(
atom: T,
update: (set: (next: T) => T) => (next: T) => T | Promise<T>,
) => {
const atomStore = new Atom<T, typeof atom.value>({
value: atom.value as T["value"],
update: atom.update,
});
const atomStore = new Atom<T>(atom);

const set = (next: T) => {
atomStore.value = next;
atomStore.subscribers.forEach((callback) => callback());
return next;
};

return createAtomImpl(atomStore, atom.value);
const assembledUpdate = update(set);
return createAtomImpl(atomStore, assembledUpdate);
};

export const create = <T extends StoreApi<T>>(init: T) => {
Expand Down
22 changes: 12 additions & 10 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import {
StoreKey,
StoreMutations,
StoreValue,
} from "./types";
} from "./types.ts";

const isAsync = <T>(call: T) =>
(call instanceof AsyncFunction &&
AsyncFunction !== Function &&
AsyncFunction !== GeneratorFunction) === true;

class Store<T extends StoreApi<T>> {
private state: StoreData<T>;
Expand Down Expand Up @@ -40,11 +45,7 @@ class Store<T extends StoreApi<T>> {
] as StoreMutations<T>[typeof mutationKey];
this.mutators[mutationKey] = mutator;

if (
(mutator instanceof AsyncFunction &&
AsyncFunction !== Function &&
AsyncFunction !== GeneratorFunction) === true
) {
if (isAsync(mutator) || isAsync(mutator(null as any))) {
const assembledMutation = async (next: StoreValue<T, keyof T>) => {
const { value } = this.assembled[key];
const mutation =
Expand All @@ -54,10 +55,10 @@ class Store<T extends StoreApi<T>> {
value as StoreValue<T, keyof T>,
)) as typeof value;

this.assembled[key] = Object.assign(this.assembled[key], {
const update = Object.assign(this.assembled[key], {
value: nextVal,
});
this.assembled = Object.assign({}, this.assembled);
this.assembled = Object.assign({}, this.assembled, update);

this.subscribers.forEach((callback) => callback());

Expand All @@ -79,10 +80,11 @@ class Store<T extends StoreApi<T>> {
value as StoreValue<T, keyof T>,
) as typeof value;

this.assembled[key] = Object.assign(this.assembled[key], {
const update = Object.assign(this.assembled[key], {
value: nextVal,
});
this.assembled = Object.assign({}, this.assembled);

this.assembled = Object.assign({}, this.assembled, update);

this.subscribers.forEach((callback) => callback());

Expand Down
38 changes: 37 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ export type StoreMutations<T extends StoreApi<T>> = Record<
>;

export type AtomStore<T> = {
value: T[keyof T];
value: T[Exclude<keyof T, "update">];
update: AtomMutation<
T[Exclude<keyof T, "update">],
T[Exclude<keyof T, "value">]
>;
};

export type MutationRequest<
Expand All @@ -89,3 +93,35 @@ export type MutationRequest<
}>;

export type Listener = () => void;

export type AtomMutation<V, P> = P extends (
set: (next: V) => V,
) => (next: V) => V
? (set: (next: V) => V) => (next: V) => V
: (set: (next: V) => V) => (next: V) => Promise<V>;

export type Mutation<
T extends StoreApi<T>,
K extends keyof T,
M extends Exclude<keyof StoreApi<T>[K], "value">,
P,
> = StoreApi<T>[K][M] &
(P extends (
set: (state: StoreValue<T, K>) => StoreValue<T, K>,
) => (next: StoreValue<T, K>) => StoreValue<T, K>
? (
set: (state: StoreValue<T, K>) => StoreValue<T, K>,
) => (next: StoreValue<T, K>) => StoreValue<T, K>
: (
set: (state: StoreValue<T, K>) => StoreValue<T, K>,
) => (next: StoreValue<T, K>) => Promise<StoreValue<T, K>>);

export type AssembledMutation<
T extends StoreApi<T>,
K extends keyof T,
M extends Exclude<keyof StoreApi<T>[K], "value">,
P,
> = StoreApi<T>[K][M] &
(P extends (next: StoreValue<T, K>) => StoreValue<T, K>
? (next: StoreValue<T, K>) => StoreValue<T, K>
: (next: StoreValue<T, K>) => Promise<StoreValue<T, K>>);
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"lib": ["dom", "dom.iterable", "esnext"],
"moduleDetection": "force",
"noUnusedLocals": true,
"noUnusedParameters": true,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"allowArbitraryExtensions": true,
Expand Down

0 comments on commit 069daed

Please sign in to comment.