Browse Source

React 18 friendly useBehavior hook

dsehnal 3 years ago
parent
commit
f040c89ab3
2 changed files with 33 additions and 10 deletions
  1. 1 0
      CHANGELOG.md
  2. 32 10
      src/mol-plugin-ui/hooks/use-behavior.ts

+ 1 - 0
CHANGELOG.md

@@ -7,6 +7,7 @@ Note that since we don't clearly distinguish between a public and private interf
 ## [Unreleased]
 
 - ModelServer ligand queries: fixes for alternate locations, additional atoms & UNL ligand
+- React 18 friendly ``useBehavior`` hook.
 
 ## [v3.6.1] - 2022-04-03
 

+ 32 - 10
src/mol-plugin-ui/hooks/use-behavior.ts

@@ -1,26 +1,23 @@
 /**
- * Copyright (c) 2020-21 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-22 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { useEffect, useRef, useState } from 'react';
+import React from 'react';
+import { skip } from 'rxjs';
 
 interface Behavior<T> {
     value: T;
     subscribe(f: (v: T) => void): { unsubscribe(): void };
 }
 
-export function useBehavior<T>(s: Behavior<T>): T;
-// eslint-disable-next-line
-export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined;
-// eslint-disable-next-line
-export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined {
-    const [, next] = useState({});
-    const current = useRef<T>();
+function useBehaviorLegacy<T>(s: Behavior<T> | undefined): T | undefined {
+    const [, next] = React.useState({});
+    const current = React.useRef<T>();
     current.current = s?.value;
 
-    useEffect(() => {
+    React.useEffect(() => {
         if (!s) {
             return;
         }
@@ -32,4 +29,29 @@ export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined {
     }, [s]);
 
     return s?.value;
+}
+
+function useBehaviorReact18<T>(s: Behavior<T> | undefined) {
+    return (React as any).useSyncExternalStore(
+        React.useCallback(
+            (callback: () => void) => {
+                const sub = (s as any)?.pipe!(skip(1)).subscribe(callback)!;
+                return () => sub?.unsubscribe();
+            },
+            [s]
+        ),
+        React.useCallback(() => s?.value, [s])
+    );
+}
+
+const _useBehavior = !!(React as any).useSyncExternalStore
+    ? useBehaviorReact18
+    : useBehaviorLegacy;
+
+export function useBehavior<T>(s: Behavior<T>): T;
+// eslint-disable-next-line
+export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined;
+// eslint-disable-next-line
+export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined {
+    return _useBehavior(s);
 }