@multisynq/react
Version:
React bindings for Multisynq
58 lines (49 loc) • 1.85 kB
text/typescript
import { ReactModel } from '../ReactModel'
import { useEffect, useState } from 'react'
import { useMultisynqContext } from './useMultisynqContext'
import hash_fn, { NotUndefined } from 'object-hash'
interface ModelState<V> {
value: V | null
hash: string | null
}
export function useModelSelector<M extends ReactModel, R extends NotUndefined>(selector: (m: M) => R): (R | null) {
// Storing the first function received in state so that we stick to the first one
// const [actualSelector] = useState<(m: M) => R>(selector)
const { session, view, model } = useMultisynqContext<M>()
// The selector function may return a pointer to an object/array/etc,
// Storing the returned value's hash allows to determine if it has changed.
// The hash must be computed when the value is set, since
// doing it at compare time would result in the same output.
const [modelState, setModelState] = useState<ModelState<R>>(() => {
if(!model) {
return { value: null, hash: null }
}
const value = selector(model)
return { value, hash: hash_fn(value) }
})
useEffect(() => {
if (!session || !view || !model) {
// If the previous state was undefined, we return the same object
setModelState((prev) => (prev.value === null ? prev : { value: null, hash: null }))
return
}
const handler = () => {
setModelState((prev) => {
const newValue = selector(model)
const newHash = hash_fn(newValue)
return prev.hash === newHash ? prev : { value: newValue, hash: newHash }
})
}
view.subscribe(
session.id,
{
event: 'react-updated',
handling: 'oncePerFrame',
},
handler
)
handler()
return () => view.unsubscribe(session.id, 'react-updated', handler)
}, [session, view, model])
return modelState.value
}