Skip to content

Commit

Permalink
Add state store manager.
Browse files Browse the repository at this point in the history
  • Loading branch information
jameswilddev committed Oct 27, 2021
1 parent 02d5f30 commit ae0e9b2
Show file tree
Hide file tree
Showing 5 changed files with 1,161 additions and 0 deletions.
125 changes: 125 additions & 0 deletions components/createStateStoreManagerComponent/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import * as React from "react";
import { useRefresh } from "../../hooks/useRefresh";
import { useEventRefresh } from "../../hooks/useEventRefresh";
import type { StateStore } from "../../services/StateStore";
import type { Json } from "../../types/Json";

/**
* Creates a React component which automatically manages a state store, loading
* it and unloading it as appropriate and passing its content down to its props.
*
* A re-render will be triggered automatically should the store's content
* change.
* @template T The type of state maintained by the state store.
* @param stateStore The state store. This must not yet be loaded.
* @returns A React component which automatically manages the state
* store, loading it and unloading it as appropriate and
* passing its content down to its props.
*/
export const createStateStoreManagerComponent = <T extends Json>(
stateStore: StateStore<T>
): React.FunctionComponent<{
readonly stateKey: null | string;
readonly unloaded: React.ReactElement<any, any> | null;
readonly loading: React.ReactElement<any, any> | null;
readonly ready: (
state: T,
setState: (to: T) => void
) => React.ReactElement<any, any> | null;
readonly unloading: React.ReactElement<any, any> | null;
}> => {
return ({ stateKey, unloaded, loading, ready, unloading }) => {
const state = React.useRef<
| { readonly type: `unloaded` }
| { readonly type: `loading`; readonly key: string }
| { readonly type: `ready`; readonly key: string }
| { readonly type: `unloading` }
>({ type: `unloaded` });

const unmounting = React.useRef(false);

const refresh = useRefresh();

const migrateState = async (forKey: null | string): Promise<void> => {
switch (state.current.type) {
case `unloaded`:
if (forKey !== null) {
state.current = { type: `loading`, key: forKey };
refresh();

await stateStore.load(forKey);

if (unmounting.current) {
await stateStore.unload();
} else {
state.current = { type: `ready`, key: forKey };
refresh();
}
}
break;

case `ready`:
if (forKey !== state.current.key) {
state.current = { type: `unloading` };
refresh();

await stateStore.unload();

state.current = { type: `unloaded` };
refresh();
}
break;
}
};

React.useEffect(() => {
migrateState(stateKey);
});

React.useEffect(() => {
return () => {
(async () => {
switch (state.current.type) {
case `loading`:
unmounting.current = true;
break;

case `ready`:
await stateStore.unload();
break;
}
})();
};
}, []);

useEventRefresh(stateStore, `set`);

switch (state.current.type) {
case `unloaded`:
if (stateKey === null) {
return unloaded;
} else {
return loading;
}

case `loading`:
if (stateKey === state.current.key) {
return loading;
} else {
return unloading;
}

case `ready`:
if (stateKey === state.current.key) {
return ready(stateStore.get(), (to: T) => {
stateStore.set(to);
});
} else {
return unloading;
}

case `unloading`:
return unloading;
}
};
};
61 changes: 61 additions & 0 deletions components/createStateStoreManagerComponent/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# `react-native-app-helpers/createStateStoreManagerComponent`

Creates a React component which automatically manages a state store, loading
it and unloading it as appropriate and passing its content down to its props.

A re-render will be triggered automatically should the store's content change.

## Usage

```tsx
import {
StateStore,
createStateStoreManagerComponent,
} from "react-native-app-helpers";

const stateStore = new StateStore<number>(0);

const StateStoreManager = createStateStoreManager(stateStore);

const ExampleScreen = () => {
const [key, setKey] = React.useState<null | string>(null);

return (
<StateStoreManager
unloaded={(
<React.Fragment>
<Button
title="Load state A"
onPress={() => {
setKey(`a`);
}}
/>
<Button
title="Load state B"
onPress={() => {
setKey(`b`);
}}
/>
</React.Fragment>
)}
loading={<Text>The state store is loading...</Text>}
ready={(state, setState) => (
<React.Fragment>
<Button
title={`State ${key}: ${state}; click or touch to increment.`}
onPress={() => {
setState(state + 1);
}}
/>
<Button
title="Unload"
onPress={() => {
setKey(null);
}}
/>
</React.Fragment>
)}
/>
);
};
```
Loading

0 comments on commit ae0e9b2

Please sign in to comment.