Last active
September 8, 2021 15:26
-
-
Save kdmadej/3a86b2d53ae5d2002632bfa7493be93c to your computer and use it in GitHub Desktop.
Observable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
}; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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