Skip to content

Prefer using react api for useProperty hook #26

@PalmZE

Description

@PalmZE

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:

  1. createSubscription + useSubscription
  2. useMutableSource (was renamed to the below)
  3. 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

  1. Looks like useSyncExternalStore is the way to go for frp-ts React integration.
  2. There is a potential issue with mid-render updates, maybe there is a smart way to prevent\fix that (not necessarily in frp-ts).
  3. Maybe there is some works needed to prepare for React 18.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions