Skip to content

Instantly share code, notes, and snippets.

@araqnid
Created January 30, 2023 19:32
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 araqnid/4c68f9cfae10eaba2f198d6de57262e8 to your computer and use it in GitHub Desktop.
Save araqnid/4c68f9cfae10eaba2f198d6de57262e8 to your computer and use it in GitHub Desktop.
Store a shared object in context and copy it to state only where subscribed
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]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment