Skip to content

Commit

Permalink
Merge pull request #3 from JuliusKoronci/feat/refactor-store-names
Browse files Browse the repository at this point in the history
Feat/refactor store names
  • Loading branch information
JuliusKoronci authored Mar 3, 2024
2 parents 8849266 + cbf94d2 commit 1e0da0a
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 16 deletions.
14 changes: 14 additions & 0 deletions e2e/counter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ test('test counter with classic store', async ({ page }) => {
const counter3Text = getStorybookLocator(page).locator(
'text=We are counting third: 0',
);
const counterPromiseText = getStorybookLocator(page).locator(
'text=We are counting hydrating default: 88',
);

await expect(counter1Text).toBeVisible();
await expect(counter2Text).toBeVisible();
await expect(counter3Text).toBeVisible();
await expect(counterPromiseText).toBeVisible();

const button3 = getStorybookLocator(page).locator(
'button:has-text("Let\'s go, third counter, reusing second store")',
Expand Down Expand Up @@ -68,4 +72,14 @@ test('test counter with classic store', async ({ page }) => {
await expect(
getStorybookLocator(page).locator('text=We are counting second: 2'),
).toBeVisible();

const buttonPromise = getStorybookLocator(page).locator(
'button:has-text("Let\'s go, promise counter")',
);
await buttonPromise.click();
await expect(
getStorybookLocator(page).locator(
'text=We are counting hydrating default: 89',
),
).toBeVisible();
});
2 changes: 1 addition & 1 deletion lib/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import * as publicApi from '../index';

describe('Public api', () => {
it('Should have a test export', () => {
expect(publicApi.buildStore).toBeDefined();
expect(publicApi.buildClassicStore).toBeDefined();
});
});
60 changes: 50 additions & 10 deletions lib/store.ts → lib/classic-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@ import { useMemo, useSyncExternalStore } from 'react';

type UpdateFunction<T> = (state: T) => T;
type Updater<T> = (cb: UpdateFunction<T>) => void;

type DefaultState<T> =
| T
| {
hydrator: () => Promise<T>
beforeLoadState: T
persist?: (state: T) => Promise<void>
};

interface StoreBuilder<T> {
defaultState: T
defaultState: DefaultState<T>
useStore: () => [T, Updater<T>]
useSelector: <S>(selector: (state: T) => S) => S
subject$: BehaviorSubject<T>
update: Updater<T>
getValue: () => T
}

const createStore = <T>(initialState: T) => {
Expand All @@ -20,12 +32,35 @@ const createStore = <T>(initialState: T) => {
};
};

export const buildStore = <T>(defaultState: T): StoreBuilder<T> => {
const getStoreInstance = createStore(defaultState);
const isDefaultState = <T>(
defaultState: DefaultState<T>
): defaultState is T => {
if (
typeof defaultState === 'object' &&
defaultState !== null &&
'hydrator' in defaultState
) {
return false;
}
return true;
};

const useStore = () => {
const store = getStoreInstance();
export const buildClassicStore = async <T>(
defaultState: DefaultState<T>
): Promise<StoreBuilder<T>> => {
const initialState = isDefaultState<T>(defaultState)
? defaultState
: await defaultState.hydrator();

const getStoreInstance = createStore(initialState);

const store = getStoreInstance();

const update: Updater<T> = (updater) => {
store.next(updater(store.getValue()));
};

const useStore = () => {
const subscribe = (onStoreChange: () => void) => {
const subscription = store.subscribe({
next: onStoreChange,
Expand All @@ -36,10 +71,11 @@ export const buildStore = <T>(defaultState: T): StoreBuilder<T> => {
subscription.unsubscribe();
};
};
const update: Updater<T> = (updater) => {
store.next(updater(store.getValue()));
};
const state: T = useSyncExternalStore(subscribe, () => store.getValue());
const state: T = useSyncExternalStore(
subscribe,
() => store.getValue(),
() => initialState
);

return [state, update] satisfies [T, Updater<T>];
};
Expand All @@ -51,9 +87,13 @@ export const buildStore = <T>(defaultState: T): StoreBuilder<T> => {

return useMemo(() => selectedValue, [selectedValue]);
};

return {
defaultState,
useStore,
useSelector
useSelector,
subject$: store,
update,
getValue: () => store.getValue()
};
};
2 changes: 1 addition & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './store';
export * from './classic-store';
16 changes: 16 additions & 0 deletions stories/counter/DefaultPromise.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Button, Flex, Text } from '@radix-ui/themes';
import React from 'react';
import useStore, { useCounterPromiseSelector } from './counterStorePromise';

export const DefaultPromise = () => {
const [, updateCount] = useStore();
const count = useCounterPromiseSelector((state) => state.count);
const increment = () =>
updateCount((prevState) => ({ ...prevState, count: prevState.count + 1 }));
return (
<Flex direction="column" gap="2">
<Text>We are counting hydrating default: {count}</Text>
<Button onClick={increment}>Let's go, promise counter</Button>
</Flex>
);
};
2 changes: 2 additions & 0 deletions stories/counter/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { Counter } from './Counter';
import React from 'react';
import { Counter2 } from './Counter2';
import { Counter3 } from './Counter3';
import { DefaultPromise } from './DefaultPromise';

export const Root = () => {
return (
<Flex direction="column" gap="2">
<Counter />
<Counter2 />
<Counter3 />
<DefaultPromise />
</Flex>
);
};
4 changes: 2 additions & 2 deletions stories/counter/counterStore.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { buildStore } from '../../lib';
import { buildClassicStore } from '../../lib';

export interface CounterState {
count: number;
}

const counterStoreBuilder = buildStore<CounterState>({ count: 0 });
const counterStoreBuilder = await buildClassicStore<CounterState>({ count: 0 });
const { useStore, useSelector: useCounterSelector } = counterStoreBuilder;

export { useCounterSelector };
Expand Down
6 changes: 4 additions & 2 deletions stories/counter/counterStore2.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { buildStore } from '../../lib';
import { buildClassicStore } from '../../lib';

export interface Counter2State {
count: number;
}

const counter2StoreBuilder = buildStore<Counter2State>({ count: 0 });
const counter2StoreBuilder = await buildClassicStore<Counter2State>({
count: 0,
});
const { useStore, useSelector: useCounter2Selector } = counter2StoreBuilder;

export { useCounter2Selector };
Expand Down
15 changes: 15 additions & 0 deletions stories/counter/counterStorePromise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { buildClassicStore } from '../../lib';

export interface CounterState {
count: number;
}

const counterStore3Builder = await buildClassicStore<CounterState>({
hydrator: () => Promise.resolve({ count: 88 }),
beforeLoadState: { count: 0 },
});
const { useStore, useSelector: useCounterPromiseSelector } =
counterStore3Builder;

export { useCounterPromiseSelector };
export default useStore;

0 comments on commit 1e0da0a

Please sign in to comment.