Skip to content

Instantly share code, notes, and snippets.

@bmingles
Last active September 19, 2021 04:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bmingles/79a87131d4590e2de80ebf99e1e2dd28 to your computer and use it in GitHub Desktop.
Save bmingles/79a87131d4590e2de80ebf99e1e2dd28 to your computer and use it in GitHub Desktop.
React Context Utils and Observable Hooks
import React from 'react'
/**
* Create a React Context that requires a value be provided explicitly (aka. no
* default value). Returns the Context Provider + a useContext hook that
* encapsulates access to the Context.
*/
export function createNamedContext<TValue>(name: string) {
const Context = React.createContext<TValue | null>(null)
function useContext(): TValue {
const value = React.useContext(Context)
if (value === null) {
throw new Error(`Context value is null for '${name}'.`)
}
return value
}
return {
Provider: Context.Provider as React.Context<TValue>['Provider'],
useContext,
}
}
type ContextValues<TProviders extends React.Context<any>['Provider'][]> = {
[P in keyof TProviders]: TProviders[P] extends React.Context<
infer V
>['Provider']
? V
: never
}
export interface ComposeProvidersProps<
TProviders extends React.Context<any>['Provider'][]
> {
values: ContextValues<TProviders>
}
/**
* Compose multiple Context Providers into one.
*/
export function composeProviders<
TProviders extends React.Context<any>['Provider'][]
>(...providers: TProviders) {
return ({
values,
children,
}: React.PropsWithChildren<
ComposeProvidersProps<TProviders>
>): JSX.Element => {
function child(i: number) {
if (i < providers.length) {
const Provider = providers[i]
return <Provider value={values[i]}>{child(i + 1)}</Provider>
}
return children
}
return <>{child(0)}</>
}
}
import React from 'react'
import { Observable, Subject } from 'rxjs'
export function useObservable<T>(observable$: Observable<T>) {
const [value, setValue] = React.useState<T | null>(null)
React.useEffect(() => {
const sub = observable$.subscribe(setValue)
return () => sub.unsubscribe()
}, [observable$])
return value
}
export function useSubject<T>(subject$: Subject<T>) {
return React.useCallback(
(value: T) => {
subject$.next(value)
},
[subject$]
)
}
export function useObservableState<T>(
subject$: Subject<T>
): [T | null, (value: T) => void]
export function useObservableState<T>(
observable$: Observable<T>,
subject$: Subject<T>
): [T | null, (value: T) => void]
export function useObservableState<T>(
observableOrSubject$: Observable<T> | Subject<T>,
subject$?: Subject<T>
): [T | null, (value: T) => void] {
const value = useObservable(observableOrSubject$)
const setValue = useSubject(subject$ ?? (observableOrSubject$ as Subject<T>))
return [value, setValue]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment