@dxos/react-hooks 0.8.4-main.84f28bd → 0.8.4-main.ae835ea

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/lib/browser/index.mjs +136 -90
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +136 -90
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/index.d.ts +4 -3
  8. package/dist/types/src/index.d.ts.map +1 -1
  9. package/dist/types/src/useAsyncEffect.d.ts +5 -29
  10. package/dist/types/src/useAsyncEffect.d.ts.map +1 -1
  11. package/dist/types/src/useControlledState.d.ts +2 -0
  12. package/dist/types/src/useControlledState.d.ts.map +1 -1
  13. package/dist/types/src/useDebugDeps.d.ts +6 -0
  14. package/dist/types/src/useDebugDeps.d.ts.map +1 -0
  15. package/dist/types/src/useDynamicRef.d.ts +6 -1
  16. package/dist/types/src/useDynamicRef.d.ts.map +1 -1
  17. package/dist/types/src/useForwardedRef.d.ts +3 -3
  18. package/dist/types/src/useForwardedRef.d.ts.map +1 -1
  19. package/dist/types/src/useIsFocused.d.ts +1 -1
  20. package/dist/types/src/useIsFocused.d.ts.map +1 -1
  21. package/dist/types/src/useMediaQuery.d.ts.map +1 -1
  22. package/dist/types/src/useMulticastObservable.test.d.ts.map +1 -1
  23. package/dist/types/src/useSignals.d.ts +10 -0
  24. package/dist/types/src/useSignals.d.ts.map +1 -0
  25. package/dist/types/src/useTimeout.d.ts +2 -1
  26. package/dist/types/src/useTimeout.d.ts.map +1 -1
  27. package/dist/types/src/useTransitions.d.ts +2 -1
  28. package/dist/types/src/useTransitions.d.ts.map +1 -1
  29. package/dist/types/src/useViewportResize.d.ts +3 -0
  30. package/dist/types/src/useViewportResize.d.ts.map +1 -0
  31. package/dist/types/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +11 -9
  33. package/src/index.ts +5 -3
  34. package/src/useAsyncEffect.ts +19 -49
  35. package/src/useControlledState.ts +2 -0
  36. package/src/useDebugDeps.ts +26 -0
  37. package/src/useDefaultValue.ts +1 -1
  38. package/src/useDynamicRef.ts +27 -5
  39. package/src/useForwardedRef.ts +17 -14
  40. package/src/useIsFocused.ts +2 -2
  41. package/src/useMediaQuery.ts +1 -2
  42. package/src/{useMulticastObservable.test.tsx → useMulticastObservable.test.ts} +1 -3
  43. package/src/useSignals.ts +27 -0
  44. package/src/useTimeout.ts +28 -3
  45. package/src/useTransitions.ts +4 -2
  46. package/src/{useResize.ts → useViewportResize.ts} +1 -1
  47. package/dist/types/src/useAsyncEffect.test.d.ts +0 -2
  48. package/dist/types/src/useAsyncEffect.test.d.ts.map +0 -1
  49. package/dist/types/src/useDebugReactDeps.d.ts +0 -6
  50. package/dist/types/src/useDebugReactDeps.d.ts.map +0 -1
  51. package/dist/types/src/useResize.d.ts +0 -3
  52. package/dist/types/src/useResize.d.ts.map +0 -1
  53. package/dist/types/src/useTrackProps.d.ts +0 -5
  54. package/dist/types/src/useTrackProps.d.ts.map +0 -1
  55. package/src/useAsyncEffect.test.tsx +0 -51
  56. package/src/useDebugReactDeps.ts +0 -27
  57. package/src/useTrackProps.ts +0 -40
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-hooks",
3
- "version": "0.8.4-main.84f28bd",
3
+ "version": "0.8.4-main.ae835ea",
4
4
  "description": "React hooks supporting DXOS React primitives.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -10,6 +10,7 @@
10
10
  "type": "module",
11
11
  "exports": {
12
12
  ".": {
13
+ "source": "./src/index.ts",
13
14
  "types": "./dist/types/src/index.d.ts",
14
15
  "browser": "./dist/lib/browser/index.mjs",
15
16
  "node": "./dist/lib/node-esm/index.mjs"
@@ -27,19 +28,20 @@
27
28
  "@preact-signals/safe-react": "^0.9.0",
28
29
  "alea": "^1.0.1",
29
30
  "lodash.defaultsdeep": "^4.6.1",
30
- "@dxos/async": "0.8.4-main.84f28bd",
31
- "@dxos/log": "0.8.4-main.84f28bd"
31
+ "mini-virtual-list": "^0.3.2",
32
+ "@dxos/async": "0.8.4-main.ae835ea",
33
+ "@dxos/log": "0.8.4-main.ae835ea"
32
34
  },
33
35
  "devDependencies": {
34
36
  "@types/lodash.defaultsdeep": "^4.6.6",
35
- "@types/react": "~18.2.0",
36
- "@types/react-dom": "~18.2.0",
37
- "react": "~18.2.0",
38
- "react-dom": "~18.2.0"
37
+ "@types/react": "~19.2.2",
38
+ "@types/react-dom": "~19.2.2",
39
+ "react": "~19.2.0",
40
+ "react-dom": "~19.2.0"
39
41
  },
40
42
  "peerDependencies": {
41
- "react": "~18.2.0",
42
- "react-dom": "~18.2.0"
43
+ "react": "^19.0.0",
44
+ "react-dom": "^19.0.0"
43
45
  },
44
46
  "publishConfig": {
45
47
  "access": "public"
package/src/index.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  export * from './useAsyncEffect';
6
6
  export * from './useAsyncState';
7
7
  export * from './useControlledState';
8
- export * from './useDebugReactDeps';
8
+ export * from './useDebugDeps';
9
9
  export * from './useDefaultValue';
10
10
  export * from './useDefaults';
11
11
  export * from './useDynamicRef';
@@ -16,7 +16,9 @@ export * from './useIsFocused';
16
16
  export * from './useMediaQuery';
17
17
  export * from './useMulticastObservable';
18
18
  export * from './useRefCallback';
19
- export * from './useResize';
19
+ export * from './useViewportResize';
20
+ export * from './useSignals';
20
21
  export * from './useTimeout';
21
- export * from './useTrackProps';
22
22
  export * from './useTransitions';
23
+
24
+ export { useSize, useScroller } from 'mini-virtual-list';
@@ -2,61 +2,31 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { useEffect } from 'react';
6
-
7
- import { log } from '@dxos/log';
5
+ import { type DependencyList, type EffectCallback, useEffect } from 'react';
8
6
 
9
7
  /**
10
- * Process async event with optional non-async destructor.
11
- * Inspired by: https://github.com/rauldeheer/use-async-effect/blob/master/index.js
12
- *
13
- * ```tsx
14
- * useAsyncEffect(async () => {
15
- * await test();
16
- * }, []);
17
- * ```
18
- *
19
- * The callback may check of the component is still mounted before doing state updates.
20
- *
21
- * ```tsx
22
- * const [value, setValue] = useState<string>();
23
- * useAsyncEffect<string>(async (isMounted) => {
24
- * const value = await test();
25
- * if (!isMounted()) {
26
- * setValue(value);
27
- * }
28
- * }, () => console.log('Unmounted'), []);
29
- * ```
30
- *
31
- * @param callback Receives a getter function that determines if the component is still mounted.
32
- * @param destructor Receives the value returned from the callback.
33
- * @param deps
34
- *
35
- * NOTE: This effect does not cancel the async operation if the component is unmounted.
36
- *
37
- * @deprecated Use useTimeout.
8
+ * Async version of useEffect.
9
+ * The `AbortController` can be used to detect if the component has been unmounted and
10
+ * can be used to propagate abort signals to downstream async operations (e.g., `fetch`).
38
11
  */
39
- export const useAsyncEffect = <T>(
40
- callback: (isMounted: () => boolean) => Promise<T> | undefined,
41
- destructor?: ((value?: T) => void) | any[],
42
- deps?: any[],
12
+ export const useAsyncEffect = (
13
+ cb: (controller: AbortController) => Promise<EffectCallback | void>,
14
+ deps?: DependencyList,
43
15
  ) => {
44
- const [effectDestructor, effectDeps] =
45
- typeof destructor === 'function' ? [destructor, deps] : [undefined, destructor];
46
-
47
16
  useEffect(() => {
48
- let mounted = true;
49
- let value: T | undefined;
50
- const asyncResult = callback(() => mounted);
51
- void Promise.resolve(asyncResult)
52
- .then((result) => {
53
- value = result;
54
- })
55
- .catch(log.catch);
17
+ const controller = new AbortController();
18
+ let cleanup: EffectCallback | void;
19
+ // NOTE: Timeout enables us to immediately cancel. if the component is unmounted.
20
+ const t = setTimeout(async () => {
21
+ if (!controller.signal.aborted) {
22
+ cleanup = await cb(controller);
23
+ }
24
+ });
56
25
 
57
26
  return () => {
58
- mounted = false;
59
- effectDestructor?.(value);
27
+ clearTimeout(t);
28
+ controller.abort();
29
+ cleanup?.();
60
30
  };
61
- }, effectDeps);
31
+ }, deps ?? []);
62
32
  };
@@ -6,6 +6,8 @@ import { type Dispatch, type SetStateAction, useEffect, useState } from 'react';
6
6
 
7
7
  /**
8
8
  * A stateful hook with a controlled value.
9
+ * NOTE: Be careful not to provide an inlinde default array.
10
+ * @deprecated Use Radix `useControllableState`.
9
11
  */
10
12
  export const useControlledState = <T>(
11
13
  controlledValue: T,
@@ -0,0 +1,26 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type DependencyList, useEffect, useRef } from 'react';
6
+
7
+ /**
8
+ * Util to log deps that have changed.
9
+ */
10
+ export const useDebugDeps = (deps: DependencyList = [], active = true) => {
11
+ const lastDeps = useRef<DependencyList>([]);
12
+ useEffect(() => {
13
+ console.group('deps changed', { previous: lastDeps.current.length, current: deps.length });
14
+ for (let i = 0; i < Math.max(lastDeps.current.length ?? 0, deps.length ?? 0); i++) {
15
+ if (lastDeps.current[i] !== deps[i] && active) {
16
+ console.log('changed', {
17
+ index: i,
18
+ previous: lastDeps.current[i],
19
+ current: deps[i],
20
+ });
21
+ }
22
+ }
23
+ console.groupEnd();
24
+ lastDeps.current = deps;
25
+ }, deps);
26
+ };
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { useEffect, useState, useMemo } from 'react';
5
+ import { useEffect, useMemo, useState } from 'react';
6
6
 
7
7
  /**
8
8
  * A custom React hook that provides a stable default value for a potentially undefined reactive value.
@@ -2,16 +2,38 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { useEffect, useRef } from 'react';
5
+ import { useCallback } from '@preact-signals/safe-react/react';
6
+ import { type Dispatch, type RefObject, type SetStateAction, useEffect, useRef, useState } from 'react';
7
+
8
+ /**
9
+ * Like `useState` but with an additional dynamic value.
10
+ */
11
+ export const useStateWithRef = <T>(valueParam: T): [T, Dispatch<SetStateAction<T>>, RefObject<T>] => {
12
+ const [value, setValue] = useState<T>(valueParam);
13
+ const valueRef = useRef<T>(valueParam);
14
+ const setter = useCallback<Dispatch<SetStateAction<T>>>((value) => {
15
+ if (typeof value === 'function') {
16
+ setValue((current) => {
17
+ valueRef.current = (value as Function)(current);
18
+ return valueRef.current;
19
+ });
20
+ } else {
21
+ valueRef.current = value;
22
+ setValue(value);
23
+ }
24
+ }, []);
25
+
26
+ return [value, setter, valueRef];
27
+ };
6
28
 
7
29
  /**
8
30
  * Ref that is updated by a dependency.
9
31
  */
10
- export const useDynamicRef = <T>(value: T) => {
11
- const ref = useRef<T>(value);
32
+ export const useDynamicRef = <T>(value: T): RefObject<T> => {
33
+ const valueRef = useRef<T>(value);
12
34
  useEffect(() => {
13
- ref.current = value;
35
+ valueRef.current = value;
14
36
  }, [value]);
15
37
 
16
- return ref;
38
+ return valueRef;
17
39
  };
@@ -2,25 +2,28 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { type ForwardedRef, useRef, useEffect } from 'react';
5
+ import { type ForwardedRef, type RefObject, useEffect, useRef } from 'react';
6
6
 
7
7
  /**
8
8
  * Combines a possibly undefined forwarded ref with a locally defined ref.
9
- * @deprecated Use @radix-ui/react-compose-refs
10
9
  */
11
- export const useForwardedRef = <T>(ref: ForwardedRef<T>) => {
12
- const innerRef = useRef<T>(null);
10
+ export const useForwardedRef = <T>(ref: ForwardedRef<T>): RefObject<T | null> => {
11
+ const innerRef = useRef<T | null>(null);
13
12
  useEffect(() => {
14
- if (!ref) {
15
- return;
16
- }
17
-
18
- if (typeof ref === 'function') {
19
- ref(innerRef.current);
20
- } else {
21
- ref.current = innerRef.current;
22
- }
23
- });
13
+ updateRef(ref, innerRef.current);
14
+ }, [ref]);
24
15
 
25
16
  return innerRef;
26
17
  };
18
+
19
+ export const updateRef = <T>(ref: ForwardedRef<T>, value: T): void => {
20
+ if (!ref) {
21
+ return;
22
+ }
23
+
24
+ if (typeof ref === 'function') {
25
+ ref(value);
26
+ } else {
27
+ ref.current = value;
28
+ }
29
+ };
@@ -5,9 +5,9 @@
5
5
  // Based upon the useIsFocused hook which is part of the `rci` project:
6
6
  /// https://github.com/leonardodino/rci/blob/main/packages/use-is-focused
7
7
 
8
- import { useEffect, useRef, useState, type RefObject } from 'react';
8
+ import { type RefObject, useEffect, useRef, useState } from 'react';
9
9
 
10
- export const useIsFocused = (inputRef: RefObject<HTMLInputElement>) => {
10
+ export const useIsFocused = (inputRef: RefObject<HTMLInputElement | null>) => {
11
11
  const [isFocused, setIsFocused] = useState<boolean | undefined>(undefined);
12
12
  const isFocusedRef = useRef<boolean | undefined>(isFocused);
13
13
 
@@ -29,8 +29,7 @@ const breakpointMediaQueries: Record<string, string> = {
29
29
  * @see Docs https://chakra-ui.com/docs/hooks/use-media-query
30
30
  */
31
31
  export const useMediaQuery = (query: string | string[], options: UseMediaQueryOptions = {}): boolean[] => {
32
- // TODO(wittjosiah): Why is the default here true?
33
- const { ssr = true, fallback } = options;
32
+ const { ssr = false, fallback } = options;
34
33
 
35
34
  const queries = (Array.isArray(query) ? query : [query]).map((query) =>
36
35
  query in breakpointMediaQueries ? breakpointMediaQueries[query] : query,
@@ -15,9 +15,7 @@ describe('useMulticastObservable', () => {
15
15
  const observable = MulticastObservable.from(event, 0);
16
16
  const { result } = renderHook(() => useMulticastObservable(observable));
17
17
  expect(result.current).toEqual(0);
18
-
19
- await act(() => event.emit(1));
20
-
18
+ act(() => event.emit(1));
21
19
  await expect.poll(() => result.current).toEqual(1);
22
20
  });
23
21
  });
@@ -0,0 +1,27 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import { computed, effect } from '@preact-signals/safe-react';
6
+ import { useRef } from '@preact-signals/safe-react/react';
7
+ import { type DependencyList, useEffect, useMemo } from 'react';
8
+
9
+ /**
10
+ * Like `useEffect` but also tracks signals inside of the callback.
11
+ */
12
+ export const useSignalsEffect = (cb: () => void | (() => void), deps?: DependencyList) => {
13
+ const callback = useRef(cb);
14
+ callback.current = cb;
15
+ useEffect(() => {
16
+ return effect(() => {
17
+ return callback.current();
18
+ });
19
+ }, deps ?? []);
20
+ };
21
+
22
+ /**
23
+ * Like `useMemo` but also tracks signals inside of the callback.
24
+ */
25
+ export const useSignalsMemo = <T>(cb: () => T, deps?: DependencyList) => {
26
+ return useMemo(() => computed(cb), deps ?? []).value;
27
+ };
package/src/useTimeout.ts CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { useEffect, useRef } from 'react';
6
6
 
7
- export const useTimeout = (callback: (() => Promise<void>) | undefined, delay = 0, deps: any[] = []) => {
7
+ export const useTimeout = (callback?: () => Promise<void>, delay = 0, deps: any[] = []) => {
8
8
  const callbackRef = useRef(callback);
9
9
  useEffect(() => {
10
10
  callbackRef.current = callback;
@@ -15,7 +15,32 @@ export const useTimeout = (callback: (() => Promise<void>) | undefined, delay =
15
15
  return;
16
16
  }
17
17
 
18
- const timeout = setTimeout(() => callbackRef.current?.(), delay);
19
- return () => clearTimeout(timeout);
18
+ const t = setTimeout(() => callbackRef.current?.(), delay);
19
+ return () => clearTimeout(t);
20
+ }, [delay, ...deps]);
21
+ };
22
+
23
+ export const useInterval = (
24
+ callback?: (() => Promise<void | boolean>) | (() => void | boolean),
25
+ delay = 0,
26
+ deps: any[] = [],
27
+ ) => {
28
+ const callbackRef = useRef(callback);
29
+ useEffect(() => {
30
+ callbackRef.current = callback;
31
+ }, [callback]);
32
+
33
+ useEffect(() => {
34
+ if (delay == null) {
35
+ return;
36
+ }
37
+
38
+ const i = setInterval(async () => {
39
+ const result = await callbackRef.current?.();
40
+ if (result === false) {
41
+ clearInterval(i);
42
+ }
43
+ }, delay);
44
+ return () => clearInterval(i);
20
45
  }, [delay, ...deps]);
21
46
  };
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { useRef, useEffect, useState } from 'react';
5
+ import { useEffect, useRef, useState } from 'react';
6
6
 
7
7
  const isFunction = <T>(functionToCheck: any): functionToCheck is (value: T) => boolean => {
8
8
  return functionToCheck instanceof Function;
@@ -48,7 +48,9 @@ export const useDidTransition = <T>(
48
48
  * Executes a callback function when a specified transition occurs in a value.
49
49
  *
50
50
  * This function utilizes the `useDidTransition` hook to monitor changes in `currentValue`.
51
- * When `currentValue` transitions from `fromValue` to `toValue`, the provided `callback` function is executed. */
51
+ * When `currentValue` transitions from `fromValue` to `toValue`, the provided `callback` function is executed.
52
+ */
53
+ // TODO(wittjosiah): Seems overwrought.
52
54
  export const useOnTransition = <T>(
53
55
  currentValue: T,
54
56
  fromValue: T | ((value: T) => boolean),
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { useLayoutEffect, useMemo } from 'react';
6
6
 
7
- export const useResize = (
7
+ export const useViewportResize = (
8
8
  handler: (event?: Event) => void,
9
9
  deps: Parameters<typeof useLayoutEffect>[1] = [],
10
10
  delay: number = 800,
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=useAsyncEffect.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useAsyncEffect.test.d.ts","sourceRoot":"","sources":["../../../src/useAsyncEffect.test.tsx"],"names":[],"mappings":""}
@@ -1,6 +0,0 @@
1
- import { type DependencyList } from 'react';
2
- /**
3
- * Util to log deps that have changed.
4
- */
5
- export declare const useDebugReactDeps: (deps?: DependencyList) => void;
6
- //# sourceMappingURL=useDebugReactDeps.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useDebugReactDeps.d.ts","sourceRoot":"","sources":["../../../src/useDebugReactDeps.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,cAAc,EAAqB,MAAM,OAAO,CAAC;AAE/D;;GAEG;AAEH,eAAO,MAAM,iBAAiB,GAAI,OAAM,cAAmB,SAc1D,CAAC"}
@@ -1,3 +0,0 @@
1
- import { useLayoutEffect } from 'react';
2
- export declare const useResize: (handler: (event?: Event) => void, deps?: Parameters<typeof useLayoutEffect>[1], delay?: number) => void;
3
- //# sourceMappingURL=useResize.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useResize.d.ts","sourceRoot":"","sources":["../../../src/useResize.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAW,MAAM,OAAO,CAAC;AAEjD,eAAO,MAAM,SAAS,GACpB,SAAS,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,EAChC,OAAM,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,CAAM,EAChD,QAAO,MAAY,SAiBpB,CAAC"}
@@ -1,5 +0,0 @@
1
- /**
2
- * Use to debug which props have changed to trigger re-renders in a React component.
3
- */
4
- export declare const useTrackProps: <T extends Record<string, unknown>>(props: T, componentName?: string, active?: boolean) => void;
5
- //# sourceMappingURL=useTrackProps.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useTrackProps.d.ts","sourceRoot":"","sources":["../../../src/useTrackProps.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7D,OAAO,CAAC,EACR,sBAA2B,EAC3B,gBAAa,SAyBd,CAAC"}
@@ -1,51 +0,0 @@
1
- //
2
- // Copyright 2022 DXOS.org
3
- //
4
-
5
- import React, { useState } from 'react';
6
- import { createRoot } from 'react-dom/client';
7
- import { act } from 'react-dom/test-utils';
8
- import { afterEach, beforeEach, describe, expect, test } from 'vitest';
9
-
10
- import { useAsyncEffect } from './useAsyncEffect';
11
-
12
- const doAsync = async <T,>(value: T) =>
13
- await new Promise<T>((resolve) => {
14
- resolve(value);
15
- });
16
-
17
- const Test = () => {
18
- const [value, setValue] = useState<string>();
19
- useAsyncEffect(async (isMounted) => {
20
- const value = await doAsync('DXOS');
21
- if (isMounted()) {
22
- void act(() => {
23
- setValue(value);
24
- });
25
- }
26
- }, []);
27
-
28
- return <h1>{value}</h1>;
29
- };
30
-
31
- let rootContainer: HTMLElement;
32
-
33
- describe('useAsyncEffect', () => {
34
- beforeEach(() => {
35
- rootContainer = document.createElement('div');
36
- document.body.appendChild(rootContainer);
37
- });
38
-
39
- afterEach(() => {
40
- document.body.removeChild(rootContainer!);
41
- });
42
-
43
- test('gets async value.', async () => {
44
- await act(() => {
45
- createRoot(rootContainer).render(<Test />);
46
- });
47
-
48
- const h1 = rootContainer.querySelector('h1');
49
- expect(h1?.textContent).toEqual('DXOS');
50
- });
51
- });
@@ -1,27 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- /* eslint-disable no-console */
6
-
7
- import { type DependencyList, useEffect, useRef } from 'react';
8
-
9
- /**
10
- * Util to log deps that have changed.
11
- */
12
- // TODO(burdon): Move to react-hooks.
13
- export const useDebugReactDeps = (deps: DependencyList = []) => {
14
- const lastDeps = useRef<DependencyList>([]);
15
- useEffect(() => {
16
- console.group('deps changed', { old: lastDeps.current.length, new: deps.length });
17
- for (let i = 0; i < Math.max(lastDeps.current.length ?? 0, deps.length ?? 0); i++) {
18
- console.log(i, lastDeps.current[i] === deps[i] ? 'SAME' : 'CHANGED', {
19
- previous: lastDeps.current[i],
20
- current: deps[i],
21
- });
22
- }
23
-
24
- console.groupEnd();
25
- lastDeps.current = deps;
26
- }, deps);
27
- };
@@ -1,40 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import { useRef, useEffect } from 'react';
6
-
7
- import { log } from '@dxos/log';
8
-
9
- /**
10
- * Use to debug which props have changed to trigger re-renders in a React component.
11
- */
12
- export const useTrackProps = <T extends Record<string, unknown>>(
13
- props: T,
14
- componentName = 'Component',
15
- active = true,
16
- ) => {
17
- const prevProps = useRef<T>(props);
18
- useEffect(() => {
19
- const changes = Object.entries(props).filter(([key]) => props[key] !== prevProps.current[key]);
20
- if (changes.length > 0) {
21
- if (active) {
22
- log.info('props changed', {
23
- componentName,
24
- keys: changes.map(([key]) => key).join(','),
25
- props: Object.fromEntries(
26
- changes.map(([key]) => [
27
- key,
28
- {
29
- from: prevProps.current[key],
30
- to: props[key],
31
- },
32
- ]),
33
- ),
34
- });
35
- }
36
- }
37
-
38
- prevProps.current = props;
39
- });
40
- };