Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
eolme committed Mar 2, 2022
1 parent 803f8e0 commit a191244
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 75 deletions.
18 changes: 3 additions & 15 deletions src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import type { Atom, Selector } from './types.js';
import { useState } from 'react';
import { constDeps, useCreation, useHandler, useIsomorphicEffect } from '@mntm/shared';

import { updater } from './store.js';

/**
* @description This is the recommended hook to use when a component intends to read computed state.
* Using this hook in a React component will subscribe the component to re-render when the computed
Expand All @@ -26,13 +24,7 @@ export const useAtomSelector = /*#__NOINLINE__*/<T, S>(atom: Readonly<Atom<T>>,
const [state, handleState] = useState(() => selector(atom.get()));

// Use layout effect for preventing race condition
useIsomorphicEffect(() => {
const handleSelect = (value: T) => handleState(selector(value));

updater.on(atom.key, handleSelect);

return () => updater.off(atom.key, handleSelect);
}, [selector]);
useIsomorphicEffect(() => atom.sub((value) => handleState(selector(value))), [selector]);

return state;
};
Expand All @@ -54,11 +46,7 @@ export const useAtomValue = /*#__NOINLINE__*/<T>(atom: Readonly<Atom<T>>) => {
const [state, handleState] = useState(atom.get);

// Use layout effect for preventing race condition
useIsomorphicEffect(() => {
updater.on(atom.key, handleState);

return () => updater.off(atom.key, handleState);
}, constDeps);
useIsomorphicEffect(() => atom.sub(handleState), constDeps);

return state;
};
Expand Down Expand Up @@ -114,7 +102,7 @@ export const useAtomState = /*#__INLINE__*/<T>(atom: Readonly<Atom<T>>) => {
* @nosideeffects
*/
export const useResetAtomState = /*#__INLINE__*/<T>(atom: Readonly<Atom<T>>) => {
return useHandler(() => atom.set(atom.default));
return useHandler(() => atom.set(atom.def));
};

/**
Expand Down
8 changes: 6 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
export {
atom,
getter,
setter,
dynamicAtom,
store,
updater
} from './store.js';

export {
resetAtoms,
hydrateAtom
} from './ssr.js';

export {
useAtomConst,
useAtomSelector,
Expand Down
11 changes: 11 additions & 0 deletions src/ssr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Atom } from './types.js';

import { store } from './store.js';

export const hydrateAtom = <T>(atom: Readonly<Atom<T>>, value: Readonly<T>) => {
store.set(atom.key, value);
};

export const resetAtoms = (atoms: Readonly<Array<Readonly<Atom<any>>>>) => {
atoms.forEach((atom) => store.set(atom.key, atom.def));
};
145 changes: 90 additions & 55 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,58 +36,6 @@ export const updater = mitt<Record<string, any>>();
*/
export const store: Store = new Map();

/**
* Utility function for creating getters.
*
* @template T Atom state type.
*
* @param key Atom's key.
*
* @returns A new getter for the current state of the atom.
*
* @example
*
* const getCounter = getter(counterAtom.key);
*
* console.log(getCounter()); // log: 0
*
* @noinline
*/
export const getter = <T>(key: string) => () => store.get(key) as T;

/**
* Utility function for creating setters.
*
* @template T Atom state type.
*
* @param key Atom's key.
*
* @returns A new setter for the current state of the atom.
*
* @example
*
* const setCounter = setter(counterAtom.key);
*
* console.log(setCounter(2)); // log: 2
*
* @noinline
*/
export const setter = <T>(key: string) => (value: AtomValOrUpdater<T>): T => {
const current = store.get(key) as T;
const next = isFunction(value) ? value(current) : value;

if (current !== next) {
store.set(key, next);

// Sync update
batch(() => {
updater.emit(key, next);
});
}

return next;
};

/**
* An atom represents state in precoil.
*
Expand All @@ -113,10 +61,97 @@ export const setter = <T>(key: string) => (value: AtomValOrUpdater<T>): T => {
export const atom = <T>(defaultValue: T, key = weakUniqueId()): Atom<T> => {
store.set(key, defaultValue);

const _get = () => store.get(key) as T;

const _set = (value: AtomValOrUpdater<T>) => {
const current = store.get(key) as T;
const next = isFunction(value) ? value(current) : value;

if (current !== next) {
store.set(key, next);

// Sync update
batch(() => {
updater.emit(key, next);
});
}

return next;
};

const _sub = (next: (value: T) => void) => {
updater.on(key, next);

return () => updater.off(key, next);
};

return {
key,
def: defaultValue,
get: _get,
set: _set,
sub: _sub
};
};

/**
* @todo TODO
*
* @param get
* @param set
* @param key
* @returns
*/
export const dynamicAtom = <T>(
get: (
get: <V>(atom: Atom<V>) => V
) => T,
set: (
get: <V>(atom: Atom<V>) => V,
set: <V>(atom: Atom<V>, value: AtomValOrUpdater<V>) => V,
arg: T
) => T,
key = weakUniqueId()
): Atom<T> => {
const depend: string[] = [];

const defaultValue = get((from) => {
const _key = from.key;

if (depend.includes(_key)) {
depend.push(_key);
}

return from.get();
});

const _get = () => get((from) => from.get());

const _set = (arg: AtomValOrUpdater<T>) => {
const next = set(
(from) => from.get(),
(from, value) => from.set(value),
isFunction(arg) ? arg(store.get(key) as T) : arg
);

store.set(key, next);

return next;
};

const _sub = (next: (value: T) => void) => {
const handle = (value: T) => next(_set(value));

depend.some((_key) => updater.on(_key, handle));

return () => depend.some((_key) => updater.off(_key, handle));
};

return {
key,
default: defaultValue,
get: getter(key),
set: setter(key)
def: defaultValue,
get: _get,
set: _set,
sub: _sub
};
};
11 changes: 8 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ export type AtomValOrUpdater<T> = T | AtomUpdater<T>;

export type Selector<T, S> = (partial: T) => S;

export type AtomGetter<T> = () => T;
export type AtomSetter<T> = (value: AtomValOrUpdater<T>) => T;
export type AtomSubscribe<T> = (set: (value: T) => void) => () => void;

export type Atom<T> = {
key: string;
default: T;
get: () => T;
set: (value: AtomValOrUpdater<T>) => T;
def: T;
get: AtomGetter<T>;
set: AtomSetter<T>;
sub: AtomSubscribe<T>;
};

0 comments on commit a191244

Please sign in to comment.