|
import React, { ReactNode, useCallback, useContext, useEffect, useRef, useState } from 'react' |
|
import { BehaviorSubject } from 'rxjs' |
|
|
|
type SharedObjectContext<T> = React.Context<BehaviorSubject<T>> |
|
|
|
type Transform<T> = (oldValue: T) => T |
|
|
|
export function createSharedObjectContext<T>(defaultValue: T): SharedObjectContext<T> { |
|
return React.createContext(new BehaviorSubject<T>(defaultValue)) |
|
} |
|
|
|
export function sharedObjectContextProvider<T>( |
|
context: SharedObjectContext<T>, |
|
): React.FC<{ children: ReactNode; initialValue: T }> { |
|
const SubjectProvider = context.Provider |
|
|
|
return ({ children, initialValue }) => { |
|
const subject = useRef<BehaviorSubject<T>>() |
|
if (!subject.current) { |
|
subject.current = new BehaviorSubject<T>(initialValue) |
|
} |
|
return <SubjectProvider value={subject.current}>{children}</SubjectProvider> |
|
} |
|
} |
|
|
|
export function useSharedObject<T>( |
|
key: SharedObjectContext<T>, |
|
): [value: T, updateValue: (transform: Transform<T>) => void] { |
|
const subject = useContext(key) |
|
if (!subject) throw new Error(`No context: ${key}`) |
|
|
|
const [value, setValue] = useState<T>(subject.value) |
|
useEffect(() => { |
|
const subscription = subject.subscribe({ |
|
next(newValue) { |
|
setValue(newValue) |
|
}, |
|
}) |
|
return () => { |
|
subscription.unsubscribe() |
|
} |
|
}, [subject, setValue]) |
|
|
|
const updateValue = useCallback( |
|
(transform: Transform<T>) => { |
|
subject.next(transform(subject.value)) |
|
}, |
|
[subject], |
|
) |
|
|
|
return [value, updateValue] |
|
} |