Hey, @raveclassic!
Intro
I'm developing a chat. It means that I need to subscribe to properties in leaves (messages, etc.) so only necessary parts re-render.
I was testing performance and noticed that old version of useProperty lost updates (new works fine). This led me to some research. Results are below.
1 React-way to subscribe to mutable sources
Look like there are three versions of "how to subscribe to external mutable source" in React:
createSubscription + useSubscription
useMutableSource (was renamed to the below)
useSyncExternalStore (package)
It turns out that managing frp-ts like source is not that easy in React because you have to take into account SSR, Suspense and async rendering (coming in React 18).
My suggestion is to use useSyncExternalStore in frp-ts/react package so the React team handles all the details (I'll attach reading list at the end for a better context).
2 State modification during render
I know that this is an anti-pattern and it should not be done (react repo issue). But, now I wonder whether this still can happen in large codebases (sockets, event handlers, whatever).
React team introduces StrictEffects and this should catch the above. Until that lands, I wonder whether deferring state.set calls as done in most eliminates the problem completely.
I still need to wrap my head around this. Maybe this case will never happen in a real application. Maybe the only way to trigger this is to directly modify the state in a render function.
Two demos showcasing this:
3 Further read
Show
Packages:
API Discussions:
react-redux migration to useSES:
mobx issues:
New version of the useProperty hook
I tested my component with the hook below and everything seems to work fine.
const bridge = <A,>(
p: Property<A>,
): {
subscribe: (onStoreChange: () => void) => () => void;
getSnapshot: () => A;
} => ({
getSnapshot: () => p.get(),
subscribe: (onChange) => {
const sub = p.subscribe({
next: () => onChange(),
});
return () => sub.unsubscribe();
},
});
const useProperty = <A,>(p: Property<A>): A => {
const { getSnapshot, subscribe } = useMemo(() => bridge(p), [p]);
return useSyncExternalStore(subscribe, getSnapshot);
};
Summary
- Looks like
useSyncExternalStore is the way to go for frp-ts React integration.
- There is a potential issue with mid-render updates, maybe there is a smart way to prevent\fix that (not necessarily in
frp-ts).
- Maybe there is some works needed to prepare for React 18.
Hey, @raveclassic!
Intro
I'm developing a chat. It means that I need to subscribe to properties in leaves (messages, etc.) so only necessary parts re-render.
I was testing performance and noticed that old version of
usePropertylost updates (new works fine). This led me to some research. Results are below.1 React-way to subscribe to mutable sources
Look like there are three versions of "how to subscribe to external mutable source" in React:
createSubscription+useSubscriptionuseMutableSource(was renamed to the below)useSyncExternalStore(package)It turns out that managing
frp-tslike source is not that easy in React because you have to take into account SSR, Suspense and async rendering (coming in React 18).My suggestion is to use
useSyncExternalStoreinfrp-ts/reactpackage so the React team handles all the details (I'll attach reading list at the end for a better context).2 State modification during render
I know that this is an anti-pattern and it should not be done (react repo issue). But, now I wonder whether this still can happen in large codebases (sockets, event handlers, whatever).
React team introduces
StrictEffectsand this should catch the above. Until that lands, I wonder whether deferringstate.setcalls as done in most eliminates the problem completely.I still need to wrap my head around this. Maybe this case will never happen in a real application. Maybe the only way to trigger this is to directly modify the state in a render function.
Two demos showcasing this:
3 Further read
Show
Packages:
API Discussions:
react-redux migration to
useSES:mobx issues:
New version of the
usePropertyhookI tested my component with the hook below and everything seems to work fine.
Summary
useSyncExternalStoreis the way to go forfrp-tsReact integration.frp-ts).