Skip to content

Commit

Permalink
Update the way stores are instantiated
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephen Paul committed Dec 22, 2021
1 parent b70ad21 commit 365ac35
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 46 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "olik-react",
"version": "0.1.1",
"version": "0.1.2",
"description": "",
"main": "dist/index.js",
"module": "dist/index.es.js",
Expand All @@ -19,7 +19,7 @@
"dependencies": {
},
"devDependencies": {
"olik": "^0.1.3",
"olik": "^0.1.4",
"@testing-library/jest-dom": "^5.11.10",
"@testing-library/react": "^11.2.6",
"@types/jest": "^26.0.7",
Expand Down
18 changes: 9 additions & 9 deletions src/core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ core.augment({
useState: function <S>(input: core.Readable<S>) {
return function (deps: React.DependencyList = []) {
const inputRef = React.useRef(input);
const [value, setValue] = React.useState(inputRef.current.read() as core.DeepReadonly<S>);
const [value, setValue] = React.useState(inputRef.current.state as core.DeepReadonly<S>);
const depsString = JSON.stringify(deps);
React.useEffect(() => {
inputRef.current = input;
setValue(input.read() as core.DeepReadonly<S>);
setValue(input.state as core.DeepReadonly<S>);
const subscription = inputRef.current.onChange(arg => setValue(arg as core.DeepReadonly<S>))
return () => subscription.unsubscribe();
}, [depsString]);
Expand All @@ -61,11 +61,11 @@ core.augment({
useState: function <C>(input: core.Derivation<C>) {
return function (deps: React.DependencyList = []) {
const inputRef = React.useRef(input);
const [value, setValue] = React.useState(inputRef.current.read() as core.DeepReadonly<C>);
const [value, setValue] = React.useState(inputRef.current.state as core.DeepReadonly<C>);
const depsString = JSON.stringify(deps);
React.useEffect(() => {
inputRef.current = input;
setValue(input.read() as core.DeepReadonly<C>);
setValue(input.state as core.DeepReadonly<C>);
const subscription = inputRef.current.onChange(arg => setValue(arg as core.DeepReadonly<C>))
return () => subscription.unsubscribe();
}, [depsString]);
Expand Down Expand Up @@ -106,18 +106,18 @@ export const useNestedStore = function <C>(
) {
const stateRef = React.useRef(arg.state);
const optionsRef = React.useRef(arg);
const select = React.useMemo(() => core.createStore(optionsRef.current.name)(stateRef.current) as any, []);
const ref = React.useMemo(() => core.nestStoreIfPossible(select, optionsRef.current), []);
const select = React.useMemo(() => core.createStore({ name: optionsRef.current.name, state: stateRef.current }), []);
const ref = React.useMemo(() => core.nestStoreIfPossible({ store: select as any, ...optionsRef.current }), []);
const selectRef = React.useRef(select);
const refRef = React.useRef(ref);
React.useEffect(() => {
// When the user saves their app (causing a hot-reload) the following sequence of events occurs:
// hook is run, useMemo (store is created), useEffect, useEffect cleanup (store is detached), hook is run, useMemo is NOT rerun (so store is NOT recreated).
// This causes the app to consume an orphaned selectRef.current which causes an error to be thrown.
// The following statement ensures that, should a nested store be orphaned, it will be re-attached to its application store
if (core.getStoreByName(optionsRef.current.containerStoreName)?.read().nested?.[optionsRef.current.name]?.[optionsRef.current.instanceName]) {
selectRef.current = core.createStore(optionsRef.current.name)(selectRef.current.read()) as any;
refRef.current = core.nestStoreIfPossible(selectRef.current, optionsRef.current);
if (core.getStoreByName(optionsRef.current.containerStoreName)?.state.nested?.[optionsRef.current.name]?.[optionsRef.current.instanceName]) {
selectRef.current = core.createStore({ name: optionsRef.current.name, state: selectRef.current.state }) as any;
refRef.current = core.nestStoreIfPossible({ store: selectRef.current as any, ...optionsRef.current });
}
return () => refRef.current.detach()
}, []);
Expand Down
77 changes: 46 additions & 31 deletions test/react.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ describe('React', () => {
};

it('should create and update a store', () => {
const select = createStore('')(initialState);
const select = createStore({ name: '', state: initialState });
select.object.property
.replace('test');
expect(select.read().object.property).toEqual('test');
expect(select.state.object.property).toEqual('test');
})

it('should useSelector', () => {
const select = createStore('')(initialState);
const select = createStore({ name: '', state: initialState });
const App = () => {
const result = select.object.property.useState();
return (
Expand All @@ -39,7 +39,7 @@ describe('React', () => {
});

it('should useDerivation with no deps', async () => {
const select = createStore('')(initialState);
const select = createStore({ name: '', state: initialState });
let calcCount = 0;
const App = () => {
const result = derive(
Expand All @@ -65,7 +65,7 @@ describe('React', () => {
});

it('should useDerivation with deps', async () => {
const get = createStore('')(initialState);
const get = createStore({ name: '', state: initialState });
let calcCount = 0;
const App = () => {
const [str, setStr] = React.useState('');
Expand Down Expand Up @@ -118,10 +118,13 @@ describe('React', () => {
});

it('should create a component store with a parent', () => {
const parentSelect = createStore('xxx')({
...initialState,
nested: {
component: {} as { [key: string]: { prop: string } }
const parentSelect = createStore({
name: 'xxx',
state: {
...initialState,
nested: {
component: {} as { [key: string]: { prop: string } }
}
}
});
let renderCount = 0;
Expand Down Expand Up @@ -152,14 +155,17 @@ describe('React', () => {
expect(renderCount).toEqual(1);
(screen.getByTestId('btn') as HTMLButtonElement).click();
expect(renderCount).toEqual(2);
expect(parentSelect.nested.component.read()).toEqual({ '0': { prop: 'test' } });
expect(parentSelect.nested.component.state).toEqual({ '0': { prop: 'test' } });
});

it('component store should receive props from parent', async () => {
const parentSelect = createStore('yyy')({
...initialState,
nested: {
component2: {} as { [key: string]: { prop: string, num: number } }
const parentSelect = createStore({
name: 'yyy',
state: {
...initialState,
nested: {
component2: {} as { [key: string]: { prop: string, num: number } }
}
}
});
const Child: React.FunctionComponent<{ num: number }> = (props) => {
Expand Down Expand Up @@ -188,11 +194,11 @@ describe('React', () => {
}
render(<Parent />);
(screen.getByTestId('btn') as HTMLButtonElement).click();
await waitFor(() => expect(parentSelect.nested.component2.read()).toEqual({ '0': { prop: 1 } }));
await waitFor(() => expect(parentSelect.nested.component2.state).toEqual({ '0': { prop: 1 } }));
})

it('should respond to async actions', async () => {
const select = createStore('')(initialState);
const select = createStore({ name: '', state: initialState });
const App = () => {
const state = select.object.property.useState();
return (
Expand All @@ -209,7 +215,7 @@ describe('React', () => {
});

it('should respond to async queries', async () => {
const select = createStore('')(initialState);
const select = createStore({ name: '', state: initialState });
const fetchString = () => new Promise<string>(resolve => setTimeout(() => resolve('test'), 10))
const App = () => {
const future = select.object.property.replace(fetchString).useFuture();
Expand All @@ -234,8 +240,11 @@ describe('React', () => {
it('should be able to paginate', async () => {
const todos = new Array(15).fill(null).map((e, i) => ({ id: i + 1, text: `value ${i + 1}` }));
type Todo = { id: Number, text: string };
const select = createStore('')({
toPaginate: {} as { [key: string]: Todo[] },
const select = createStore({
name: '',
state: {
toPaginate: {} as { [key: string]: Todo[] },
}
});
const fetchTodos = (index: number) => new Promise<Todo[]>(resolve => setTimeout(() => resolve(todos.slice(index * 10, (index * 10) + 10)), 10));
const App = () => {
Expand Down Expand Up @@ -270,8 +279,11 @@ describe('React', () => {
})

it('should be able to paginate', async () => {
const select = createStore('')({
storeNum: -1
const select = createStore({
name: '',
state: {
storeNum: -1
}
});
const fetchNum = (num: number) => new Promise<number>(resolve => setTimeout(() => resolve(num), 100));
const App = () => {
Expand All @@ -298,7 +310,7 @@ describe('React', () => {
})

it('should support optimistic updates correctly with a future', async () => {
const select = createStore('')({ test: '' });
const select = createStore({ name: '', state: { test: '' } });
const App = () => {
const future = select.test
.replace(() => new Promise(resolve => resolve('XXX')), { optimisticallyUpdateWith: 'ABC' })
Expand All @@ -315,7 +327,7 @@ describe('React', () => {
})

it('should support optimistic updates correctly with a promise', async () => {
const select = createStore('')({ test: '' });
const select = createStore({ name: '', state: { test: '' } });
const App = () => {
const onClick = () => select.test
.replace(() => new Promise(resolve => resolve('XXX')), { optimisticallyUpdateWith: 'ABC' });
Expand All @@ -334,13 +346,16 @@ describe('React', () => {
})

it('should useState with deps correctly', async () => {
const select = createStore('')({
todos: [
{ id: 1, title: "mix flour", done: true },
{ id: 2, title: "add egg", done: false },
{ id: 3, title: "bake cookies", done: false }
],
showCompleted: false
const select = createStore({
name: '',
state: {
todos: [
{ id: 1, title: "mix flour", done: true },
{ id: 2, title: "add egg", done: false },
{ id: 3, title: "bake cookies", done: false }
],
showCompleted: false
}
});
const App = () => {
const showCompleted = select.showCompleted
Expand All @@ -353,7 +368,7 @@ describe('React', () => {
data-testid="checkbox"
type="checkbox"
checked={showCompleted}
onChange={e => select.showCompleted.replace(e.target.checked) }
onChange={e => select.showCompleted.replace(e.target.checked)}
/>
Show completed todos
<hr />
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3725,10 +3725,10 @@ object.values@^1.1.4:
define-properties "^1.1.3"
es-abstract "^1.18.2"

olik@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/olik/-/olik-0.1.3.tgz#ea5ae23fd13db4012a521df7fa165290f176210a"
integrity sha512-Ja7GVaOmy3ML/pGAtaNW6Ct/7nxz07w4KieI7+6R6nwmLLbtsR9WveCl3I/MMbezuaSGW8o4L8csazDMSN4s8g==
olik@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/olik/-/olik-0.1.4.tgz#6c163182c655b22b8b131fb98dd9ae6a5098c9e3"
integrity sha512-Tq+S2x4EoQOVra9zCPUxjnztVXuR5eVsYlTuedgnQY3Xw5al9uRZJUOMuAmGLNM/wDi2e4xXpN8oZyhhhCiLwA==

once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
Expand Down

0 comments on commit 365ac35

Please sign in to comment.