Skip to content

Instantly share code, notes, and snippets.

@mukaschultze
Created September 5, 2021 19:39
Show Gist options
  • Save mukaschultze/47f11a033bd44bf3ec95e433bad01d04 to your computer and use it in GitHub Desktop.
Save mukaschultze/47f11a033bd44bf3ec95e433bad01d04 to your computer and use it in GitHub Desktop.
RxJS hooks for React
import { useEffect, useRef, useState } from "react";
import { BehaviorSubject, Observable, PartialObserver, Subject } from "rxjs";
function useConstant<T>(factory: () => T): T {
const ref = useRef<{ value: T }>();
if (!ref.current) {
ref.current = { value: factory() };
}
return ref.current.value;
}
/** Create an Observable from an observable factory */
export function useObservable<T>(observableFactory: () => Observable<T>) {
return useConstant(observableFactory);
}
/** Subscribe to an Observable and return an stateful tuple containing the last
* value emitted, the error (if any) and a boolean indicating if the Observable
* has completed.
*/
export function useSubscription<T>(
observableFactory: () => Observable<T>
): [T | null, any | undefined, boolean];
export function useSubscription<T>(
observableFactory: () => Observable<T>,
defaultValue?: T | (() => T)
): [T, any | undefined, boolean];
export function useSubscription<T>(
observableFactory: () => Observable<T>,
defaultValue?: T | (() => T)
) {
const [value, setValue] = useState<T | null>(defaultValue ?? null);
const [error, setError] = useState<any | undefined>(undefined);
const [complete, setComplete] = useState<boolean>(false);
useSubscribe(observableFactory, {
next: (value) => setValue(value),
error: (error) => setError(error),
complete: () => setComplete(true),
});
return [value, error, complete] as const;
}
/** Subscribe to the emissions of an Observable, similar to useEffect */
export function useSubscribe<T>(
observableFactory: () => Observable<T>,
partialObserver?: PartialObserver<T>
) {
const observable = useObservable(observableFactory);
useEffect(() => {
const subscription = observable.subscribe(partialObserver);
return () => subscription.unsubscribe();
}, [observable]);
}
/** Creates a new Subject that completes on component unmount */
export function useSubject<T>() {
const sub = useConstant(() => new Subject<T>());
useEffect(() => () => sub.complete(), [sub]);
return sub;
}
/** Returns a tuple containing a stateful value, a setter function and an
* BehaviorSubject, this is similar to using using useState
*/
export function useBehaviorSubject<T>(defaultValue: T | (() => T)) {
const [value, setValue] = useState(defaultValue);
const sub = useConstant(() => new BehaviorSubject<T>(value));
useEffect(() => () => sub.complete(), [sub]);
useSubscribe(() => sub, { next: setValue });
return [value, sub.next, sub] as const;
}
/** Create an Observable that emits when a React dependency changes */
export function useFromDependency<T>(dependency: T) {
const sub = useSubject<T>();
const obs = useConstant(() => sub.asObservable());
useEffect(() => sub.next(dependency), [dependency, sub]);
return obs;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment