|
import React, { Component, Dispatch, SetStateAction, useEffect, useState } from "react"; |
|
|
|
// Define a global State object |
|
export type State = { |
|
/* "username": string */ |
|
} |
|
|
|
export class Store<T> { |
|
|
|
private data: T = {} as T; |
|
private subscriptions: { [x: string]: { [x: string]: (x: any) => Promise<void> | void }} = {}; |
|
|
|
constructor(init: T) { |
|
this.data = init; |
|
} |
|
|
|
public async fromString(s: string) { |
|
const update = JSON.parse(s); |
|
for (const key in Object.keys(update)) { |
|
await this.set(key as keyof T, update[key]); |
|
} |
|
} |
|
|
|
public toString(): string { |
|
return JSON.stringify(this.data); |
|
} |
|
|
|
public async set<K extends keyof T>(key: K, val: T[K]) { |
|
if (this.data[key] === val) { |
|
return; |
|
} |
|
|
|
this.data[key] = val; |
|
if (!this.subscriptions[key as string]) { |
|
this.subscriptions[key as string] = {}; |
|
} |
|
|
|
await Promise.all(Object |
|
.keys(this.subscriptions[key as string]) |
|
.map((k) => this.subscriptions[key as string][k]) |
|
.map((fn) => fn(val))); |
|
} |
|
|
|
public get<K extends keyof T>(key: K): T[K] { |
|
return this.data[key]; |
|
} |
|
|
|
public sub<K extends keyof T>(key: K, fn: (x: T[K]) => Promise<void> | void): () => void { |
|
const id = `${Math.random() * 1000}`; |
|
if (!this.subscriptions[key as string]) { |
|
this.subscriptions[key as string] = {}; |
|
} |
|
|
|
this.subscriptions[key as string][id] = fn; |
|
|
|
return (() => |
|
delete this.subscriptions[key as string][id] |
|
).bind(this); |
|
} |
|
} |
|
|
|
|
|
export default class StateService extends Component { |
|
|
|
public static getStore(): Store<Partial<State>> { |
|
return StateService.Store; |
|
} |
|
|
|
public static async saveCurrentState() { |
|
/* await AsyncStorage.setItem("state", StateService.Store.toString()); Save current state to disk */ |
|
} |
|
|
|
public static async loadCurrentState() { |
|
/* const state = await AsyncStorage.getItem("state"); Load state from disk */ |
|
if (state != null) { |
|
StateService.Store.fromString(state); |
|
} |
|
} |
|
|
|
// monostate store |
|
private static Store = new Store<Partial<State>>({}); |
|
|
|
public async componentDidMount() { |
|
await StateService.loadCurrentState(); |
|
} |
|
|
|
public async componentWillUnmount() { |
|
await StateService.saveCurrentState(); |
|
} |
|
|
|
public render() { |
|
/* return (<div></div>); Expose something that can be rendered */ |
|
} |
|
} |
|
|
|
// behaves just like setState, only that global state changes are locally propagated |
|
export function useReflectedState<K extends keyof State>(key: K): [Partial<State>[K], Dispatch<SetStateAction<State[K]>>] { |
|
|
|
// base on an actual useState Hook, initialize with existing state |
|
const [val, setVal] = useState(StateService.getStore().get(key)); |
|
|
|
// sub returns a function to cancel the subscription, which works very well in this context (useEffect Hook) |
|
useEffect(() => StateService.getStore().sub(key, (update: Partial<State>[K]) => { |
|
setVal(update); |
|
}), []); |
|
|
|
return [ |
|
val, |
|
(value: SetStateAction<State[K]>) => { |
|
StateService.getStore().set(key, value as State[K]); |
|
}, |
|
]; |
|
} |