Skip to content

Commit

Permalink
feat: useSelector (#6)
Browse files Browse the repository at this point in the history
* feat: useSelector

* update CHANGELOG
  • Loading branch information
dai-shi authored Sep 9, 2024
1 parent 30304bd commit 2e990a4
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 20 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- feat: useSelector #6

## [0.5.0] - 2024-09-09

### Added
Expand Down
5 changes: 2 additions & 3 deletions examples/02_create/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useCallback } from 'react';
import { create } from 'jotai-zustand';

const useCountStore = create(
Expand All @@ -11,8 +10,8 @@ const useCountStore = create(
);

const Counter = () => {
const count = useCountStore(useCallback((state) => state.count, []));
const inc = useCountStore(useCallback((state) => state.inc, []));
const count = useCountStore((state) => state.count);
const inc = useCountStore((state) => state.inc);

return (
<>
Expand Down
22 changes: 5 additions & 17 deletions src/create.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { useMemo } from 'react';
import { atom, createStore } from 'jotai/vanilla';
import { useAtomValue } from 'jotai/react';

import { atomWithActions } from './atomWithActions.js';
import { useSelector } from './useSelector.js';

export function create<State extends object, Actions extends object>(
initialState: State,
Expand All @@ -11,18 +8,9 @@ export function create<State extends object, Actions extends object>(
get: () => State,
) => Actions,
) {
const store = createStore();
const theAtom = atomWithActions(initialState, createActions);
const useStore = <Slice>(selector: (state: State & Actions) => Slice) => {
const derivedAtom = useMemo(
() => atom((get) => selector(get(theAtom))),
[selector],
);
return useAtomValue(derivedAtom, { store });
};
const useStoreWithGetState = useStore as typeof useStore & {
getState: () => State & Actions;
};
useStoreWithGetState.getState = () => store.get(theAtom);
return useStoreWithGetState;
return <Slice>(
selector: (state: State & Actions) => Slice,
equalityFn?: (a: Slice, b: Slice) => boolean,
) => useSelector(theAtom, selector, equalityFn);
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { atomWithStore } from './atomWithStore.js';
export { atomWithActions } from './atomWithActions.js';
export { useSelector } from './useSelector.js';
export { create } from './create.js';
65 changes: 65 additions & 0 deletions src/useSelector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useEffect, useReducer } from 'react';
import type { ReducerWithoutAction } from 'react';
import type { Atom, ExtractAtomValue } from 'jotai/vanilla';
import { useStore } from 'jotai/react';

type Store = ReturnType<typeof useStore>;

type Options = Parameters<typeof useStore>[0];

export function useSelector<Value, Slice>(
atom: Atom<Value>,
selector: (value: Value) => Slice,
equalityFn?: (a: Slice, b: Slice) => boolean,
options?: Options,
): Awaited<Slice>;

export function useSelector<AtomType extends Atom<unknown>, Slice>(
atom: AtomType,
selector: (value: ExtractAtomValue<AtomType>) => Slice,
equalityFn?: (a: Slice, b: Slice) => boolean,
options?: Options,
): Awaited<Slice>;

export function useSelector<Value, Slice>(
atom: Atom<Value>,
selector: (value: Value) => Slice,
equalityFn: (a: Slice, b: Slice) => boolean = Object.is,
options?: Options,
) {
const store = useStore(options);

const [[sliceFromReducer, storeFromReducer, atomFromReducer], rerender] =
useReducer<
ReducerWithoutAction<readonly [Slice, Store, typeof atom]>,
undefined
>(
(prev) => {
const nextSlice = selector(store.get(atom));
if (
equalityFn(prev[0], nextSlice) &&
prev[1] === store &&
prev[2] === atom
) {
return prev;
}
return [nextSlice, store, atom];
},
undefined,
() => [selector(store.get(atom)), store, atom],
);

let slice = sliceFromReducer;
if (storeFromReducer !== store || atomFromReducer !== atom) {
rerender();
slice = selector(store.get(atom));
}

useEffect(() => {
const unsub = store.sub(atom, () => rerender());
rerender();
return unsub;
}, [store, atom]);

return slice;
}

0 comments on commit 2e990a4

Please sign in to comment.