Last active
April 2, 2019 15:40
-
-
Save jonastreub/14a876b80ea8d89719d30ed22cf02248 to your computer and use it in GitHub Desktop.
Share values among components using a hook
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
import * as React from "react" | |
// Share values among components using a hook. The values are available using an identifier. | |
// There are currently three types of build-in tokens: | |
// - useNumberToken | |
// - useStringToken | |
// - useBooleanToken | |
// - useObjectToken | |
// - useArrayToken | |
// Example using hardcoded identifier: | |
// const [filter] = useStringToken("listFilter") | |
// Example using dynamic identfier: | |
// const [value, setValue] = useNumberToken(identifier, 0) | |
export class ReferenceCountedStore<T> { | |
private _value: T | |
private deleter: () => void | |
constructor(initialValue: T, deleter: () => void) { | |
this._value = initialValue | |
this.deleter = deleter | |
} | |
get value() { | |
return this._value | |
} | |
set value(newValue: T) { | |
this._value = newValue | |
this.updateHandlers.forEach(update => update()) | |
} | |
private updateHandlers: (() => void)[] = [] | |
addUpdateHandler(update: () => void) { | |
this.updateHandlers.push(update) | |
} | |
removeUpdateHandler(update: () => void) { | |
this.updateHandlers = this.updateHandlers.filter( | |
handler => handler !== update | |
) | |
if (this.updateHandlers.length === 0) { | |
this.deleter() | |
} | |
} | |
} | |
export class TokenManager<T> { | |
stores = new Map<string, ReferenceCountedStore<T>>() | |
getStore(identifier: string, initialValue?: T) { | |
if (this.stores.has(identifier)) { | |
return this.stores.get(identifier) | |
} else { | |
const store = new ReferenceCountedStore<T>(initialValue, () => | |
this.deleteStore(identifier) | |
) | |
this.stores.set(identifier, store) | |
return store | |
} | |
} | |
private deleteStore(identifier: string) { | |
this.stores.delete(identifier) | |
} | |
} | |
export function generateUseToken<T>( | |
manager: TokenManager<T>, | |
isValidValue: (value: any) => boolean, | |
defaultInitialValue: T | |
) { | |
return function( | |
identifier: string, | |
initialValue?: T | |
): [T, (newValue: T) => void] { | |
const [_, setUpdate] = React.useState(0) | |
const store = manager.getStore( | |
identifier, | |
isValidValue(initialValue) ? initialValue : defaultInitialValue | |
) | |
React.useEffect(() => { | |
function updateHandler() { | |
setUpdate(current => current + 1) | |
} | |
store.addUpdateHandler(updateHandler) | |
return () => store.removeUpdateHandler(updateHandler) | |
}, [store]) | |
return [ | |
store.value, | |
newValue => { | |
if (isValidValue(newValue)) store.value = newValue | |
}, | |
] | |
} | |
} | |
// Managers | |
const numberTokenManager = new TokenManager<number>() | |
const stringTokenManager = new TokenManager<string>() | |
const booleanTokenManager = new TokenManager<boolean>() | |
const objectTokenManager = new TokenManager<{ [key: string]: any }>() | |
const arrayTokenManager = new TokenManager<any[]>() | |
// API | |
export const useNumberToken = generateUseToken(numberTokenManager, isNumber, 0) | |
export const useStringToken = generateUseToken(stringTokenManager, isString, "") | |
export const useBooleanToken = generateUseToken( | |
booleanTokenManager, | |
isBoolean, | |
true | |
) | |
export const useObjectToken = generateUseToken(objectTokenManager, isObject, {}) | |
export const useArrayToken = generateUseToken(arrayTokenManager, isArray, []) | |
// Validators | |
function isNumber(value: any) { | |
return typeof value === "number" && Number.isFinite(value) | |
} | |
function isBoolean(value: any) { | |
return value === true || value === false | |
} | |
function isString(value: any) { | |
return typeof value === "string" | |
} | |
function isObject(value: any) { | |
return typeof value === "object" && value && !Array.isArray(value) | |
} | |
function isArray(value: any) { | |
return typeof value === "object" && value && Array.isArray(value) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment