Skip to content

Commit

Permalink
[hooks] Purify useCreatePersister
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesgpearce committed Jan 18, 2024
1 parent 16c32b7 commit de56cd8
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 55 deletions.
27 changes: 12 additions & 15 deletions src/types/docs/ui-react.js
Original file line number Diff line number Diff line change
Expand Up @@ -9366,31 +9366,28 @@
*
* It is possible to create a Persister outside of the React app with the
* regular createPersister function and pass it in, but you may prefer to create
* it within the app, perhaps inside the top-level component. To defend against
* a new Persister being created every time the app renders or re-renders, the
* useCreatePersister hook wraps the creation in a memoization, and provides a
* second callback so that you can configure the Persister, once, and
* asynchronously, when it is created.
* it within the app, perhaps inside the top-level component. To prevent a new
* Persister being created every time the app renders or re-renders, since v5.0
* the useCreateMetrics hook performs the creation in an effect.
*
* If your `create` function (the second parameter to the hook) contains
* dependencies, the changing of which should cause the Persister to be
* recreated, you can provide them in an array in the third parameter, just as
* you would for any React hook with dependencies. The Store passed in as the
* first parameter of this hook is used as a dependency by default.
*
* A second `then` callback can be provided as the fourth parameter. This is
* called after the creation, and, importantly, can be asynchronous, so that you
* can configure the Persister with the startAutoLoad method and startAutoSave
* method, for example. If this callback contains dependencies, the changing of
* which should cause the Persister to be reconfigured, you can provide them in
* an array in the fifth parameter. The Persister itself is used as a dependency
* by default.
* A second callback, called `then`, can be provided as the fourth parameter.
* This is called after the creation, and, importantly, can be asynchronous, so
* that you can configure the Persister with the startAutoLoad method and
* startAutoSave method, for example. If this callback contains dependencies,
* the changing of which should cause the Persister to be reconfigured, you can
* provide them in an array in the fifth parameter. The Persister itself is used
* as a dependency by default.
*
* Since v4.3.0, the `create` function can return undefined, meaning that you
* can enable or disable persistence conditionally within this hook. This is
* useful for applications which might turn on or off their cloud persistence or
* collaboration features. As a result, the `then` callback may also get passed
* undefined, which you should handle accordingly.
* collaboration features.
*
* Since v4.3.19, a `destroy` function can be provided which will be called
* after an old Persister is destroyed due to a change in the `createDeps`
Expand Down Expand Up @@ -9509,8 +9506,8 @@
* // -> '<span>{\"pets\":{\"fido\":{\"species\":\"dog\"}}}</span>'
*
* root.render(<App sessionKey="cujoStore" />); // !act
* // -> 'Persister created for session key cujoStore'
* // -> 'Persister destroyed for session key fidoStore'
* // -> 'Persister created for session key cujoStore'
*
* // ... // !act
* console.log(app.innerHTML);
Expand Down
4 changes: 2 additions & 2 deletions src/types/ui-react.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -959,9 +959,9 @@ export function useCreatePersister<
store: Store,
create: (store: Store) => PersisterOrUndefined,
createDeps?: React.DependencyList,
then?: (persister: PersisterOrUndefined) => Promise<void>,
then?: (persister: Persister) => Promise<void>,
thenDeps?: React.DependencyList,
destroy?: (persister: PersisterOrUndefined) => void,
destroy?: (persister: Persister) => void,
destroyDeps?: React.DependencyList,
): PersisterOrUndefined;

Expand Down
4 changes: 2 additions & 2 deletions src/types/with-schemas/ui-react.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1143,9 +1143,9 @@ export type WithSchemas<Schemas extends OptionalSchemas> = {
store: Store<Schemas>,
create: (store: Store<Schemas>) => PersisterOrUndefined,
createDeps?: React.DependencyList,
then?: (persister?: PersisterOrUndefined) => Promise<void>,
then?: (persister: Persister<Schemas>) => Promise<void>,
thenDeps?: React.DependencyList,
destroy?: (persister: PersisterOrUndefined) => void,
destroy?: (persister: Persister<Schemas>) => void,
destroyDeps?: React.DependencyList,
) => PersisterOrUndefined;

Expand Down
33 changes: 17 additions & 16 deletions src/ui-react/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1741,32 +1741,33 @@ export const useCreatePersister: typeof useCreatePersisterDecl = <
store: Store,
create: (store: Store) => PersisterOrUndefined,
createDeps: React.DependencyList = EMPTY_ARRAY,
then?: (persister: PersisterOrUndefined) => Promise<void>,
then?: (persister: Persister) => Promise<void>,
thenDeps: React.DependencyList = EMPTY_ARRAY,
destroy?: (persister: PersisterOrUndefined) => void,
destroy?: (persister: Persister) => void,
destroyDeps: React.DependencyList = EMPTY_ARRAY,
): PersisterOrUndefined => {
const [, rerender] = useState<[]>();
const persister = useMemo(
() => create(store) as any,
// eslint-disable-next-line react-hooks/exhaustive-deps
[store, ...createDeps],
);
const [persister, setPersister] = useState<any>();

useEffect(
() => {
(async () => {
const newPersister = create(store);
setPersister(newPersister);
if (newPersister) {
if (then) {
await then(persister);
rerender([]);
(async () => {
await then(newPersister);
rerender([]);
})();
}
})();
return () => {
persister?.destroy();
destroy?.(persister);
};
return () => {
newPersister.destroy();
destroy?.(newPersister);
};
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[persister, ...thenDeps, ...destroyDeps],
[store, ...createDeps, ...thenDeps, ...destroyDeps],
);
return persister;
};
53 changes: 33 additions & 20 deletions test/unit/ui-react/hooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -407,9 +407,11 @@ describe('Create Hooks', () => {
});
const Test = ({id}: {id: number}) => {
const store = useCreateStore(initStore);
const persister = useCreatePersister(store, createPersister);
const persister = useCreatePersister(store, createPersister, [id]);
const cell = useCell('t1', 'r1', 'c1', store);
return didRender(<>{JSON.stringify([id, persister.getStats(), cell])}</>);
return didRender(
<>{JSON.stringify([id, persister?.getStats(), cell])}</>,
);
};
act(() => {
renderer = create(<Test id={1} />);
Expand All @@ -423,9 +425,18 @@ describe('Create Hooks', () => {
expect(renderer.toJSON()).toEqual(
JSON.stringify([1, {loads: 1, saves: 0}, 1]),
);
act(() => {
renderer.update(<Test id={2} />);
});
await act(async () => {
await pause();
});
expect(renderer.toJSON()).toEqual(
JSON.stringify([2, {loads: 0, saves: 0}, 1]),
);
expect(initStore).toHaveBeenCalledTimes(1);
expect(createPersister).toHaveBeenCalledTimes(1);
expect(didRender).toHaveBeenCalledTimes(2);
expect(createPersister).toHaveBeenCalledTimes(2);
expect(didRender).toHaveBeenCalledTimes(5);
_persister?.stopAutoLoad()?.stopAutoSave();
});
});
Expand All @@ -435,9 +446,11 @@ test('useCreatePersister, then, no destroy', async () => {
tmp.setGracefulCleanup();
const fileName = tmp.fileSync().name;
const initStore = jest.fn(createStore);
const createPersister = jest.fn((store: Store) => {
_persister = createFilePersister(store, fileName);
return _persister;
const createPersister = jest.fn((store: Store, id: number) => {
if (id != 0) {
_persister = createFilePersister(store, fileName);
return _persister;
}
});
const initPersister = jest.fn(async (persister: Persister, id: number) => {
await persister.load({t1: {r1: {c1: id}}});
Expand All @@ -446,33 +459,35 @@ test('useCreatePersister, then, no destroy', async () => {
const store = useCreateStore(initStore);
const persister = useCreatePersister(
store,
(store) => createPersister(store),
undefined,
(store) => createPersister(store, id),
[id],
async (persister) => await initPersister(persister, id),
[id],
);
return didRender(<>{JSON.stringify([id, persister.getStats()])}</>);
return didRender(<>{JSON.stringify([id, persister?.getStats()])}</>);
};
act(() => {
renderer = create(<Test id={0} />);
});
expect(renderer.toJSON()).toEqual(JSON.stringify([0, null]));
act(() => {
renderer = create(<Test id={1} />);
});
expect(renderer.toJSON()).toEqual(JSON.stringify([1, {loads: 0, saves: 0}]));
await act(async () => {
await pause();
});
expect(renderer.toJSON()).toEqual(JSON.stringify([1, {loads: 1, saves: 0}]));
act(() => {
renderer.update(<Test id={2} />);
});
expect(renderer.toJSON()).toEqual(JSON.stringify([2, {loads: 1, saves: 0}]));
await act(async () => {
await pause();
});
expect(renderer.toJSON()).toEqual(JSON.stringify([2, {loads: 2, saves: 0}]));
expect(initStore).toHaveBeenCalledTimes(1);
expect(createPersister).toHaveBeenCalledTimes(1);
expect(renderer.toJSON()).toEqual(JSON.stringify([2, {loads: 1, saves: 0}]));
expect(initStore).toHaveBeenCalledTimes(2);
expect(createPersister).toHaveBeenCalledTimes(3);
expect(initPersister).toHaveBeenCalledTimes(2);
expect(didRender).toHaveBeenCalledTimes(4);
expect(didRender).toHaveBeenCalledTimes(7);
_persister?.stopAutoLoad()?.stopAutoSave();
});

Expand Down Expand Up @@ -502,20 +517,18 @@ test('useCreatePersister, then, destroy', async () => {
[id],
destroyPersister,
);
return didRender(<>{JSON.stringify([id, persister.getStats()])}</>);
return didRender(<>{JSON.stringify([id, persister?.getStats()])}</>);
};
act(() => {
renderer = create(<Test id={1} />);
});
expect(renderer.toJSON()).toEqual(JSON.stringify([1, {loads: 0, saves: 0}]));
await act(async () => {
await pause();
});
expect(renderer.toJSON()).toEqual(JSON.stringify([1, {loads: 1, saves: 0}]));
act(() => {
renderer.update(<Test id={2} />);
});
expect(renderer.toJSON()).toEqual(JSON.stringify([2, {loads: 0, saves: 0}]));
await act(async () => {
await pause();
});
Expand All @@ -525,7 +538,7 @@ test('useCreatePersister, then, destroy', async () => {
expect(initPersister).toHaveBeenCalledTimes(2);
expect(destroyPersister).toHaveBeenCalledTimes(1);
expect(destroyPersister).toHaveBeenCalledWith(persisters[1]);
expect(didRender).toHaveBeenCalledTimes(4);
expect(didRender).toHaveBeenCalledTimes(6);
persisters.forEach((persister) => persister.stopAutoLoad().stopAutoSave());
});

Expand Down

0 comments on commit de56cd8

Please sign in to comment.