Skip to content

Instantly share code, notes, and snippets.

@kdmadej
Last active September 8, 2021 15:26
Show Gist options
  • Save kdmadej/3a86b2d53ae5d2002632bfa7493be93c to your computer and use it in GitHub Desktop.
Save kdmadej/3a86b2d53ae5d2002632bfa7493be93c to your computer and use it in GitHub Desktop.
Observable
type Callback<T> = (newValue: T, oldValue: T) => void;
type SetDeleteResult<T> = ReturnType<Set<T>['delete']>
type CleanUp<T> = () => SetDeleteResult<T>;
export type UnwrappedObservable<T> = T extends Observable<infer R> ? R : never;
export interface Observable<T> {
get(): T;
set(newValue: T): void;
reset(): void;
size(): number;
subscribe(callback: Callback<T>): CleanUp<T>;
unsubscribe(callback: Callback<T>): SetDeleteResult<T>;
}
export const observable = <T>(value: T): Observable<T> => {
const subscriptions = new Set<Callback<T>>();
let state = value;
const size: Observable<T>['size'] = () => subscriptions.size;
const get: Observable<T>['get'] = () => state;
const set: Observable<T>['set'] = (newValue: T) => {
// NOTE: this is sth like `onBeforeChange`, since we update the value after the callbacks are triggered
subscriptions.forEach(callback => callback(newValue, state));
state = newValue;
};
const reset: Observable<T>['reset'] = () => set(value);
const subscribe: Observable<T>['subscribe'] = (callback) => {
subscriptions.add(callback);
return () => subscriptions.delete(callback);
};
const unsubscribe: Observable<T>['unsubscribe']
= callback => subscriptions.delete(callback);
return {
get,
set,
reset,
size,
subscribe,
unsubscribe,
};
};
/**
* Mothafuckin' epic state management tool for the PROs!
*
* @param observable - some mothafuckin' shit you wanna keep track of BRO!
*/
export const useObservable = <T>(observable: Observable<T>): [T, (state: T) => void] => {
// prepare React component state we'll use to update the component
// when the associated observable's state updates
const [state, setState] = React.useState(observable.get());
React.useEffect(
() => {
// in case `observable` dependency changes and we want to sync the states
// (can't reinitialize `useState`) – should not do anything on first call
// as we're setting the same value as the one we initialized with
setState(observable.get());
// `.subscribe()` returns a clean up function that removes the callback
const cleanUp = observable.subscribe((newState) => {
setState(newState);
});
return () => { cleanUp(); };
},
[observable],
);
// NOTE: old approach – `setObservableState()` seems redundant as it does the exact same thing `observable.set()`
// does but through an additional call that has to be redefined to keep in sync with the observable
// const setObservableState = React.useCallback((newState: T) => observable.set(newState), [observable]);
// return [state, setObservableState];
return [state, observable.set];
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment