Skip to content

Commit

Permalink
useWatch usage tracking and optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
a-type committed Dec 16, 2024
1 parent b66b6ed commit cacc5b0
Show file tree
Hide file tree
Showing 16 changed files with 8,711 additions and 9,944 deletions.
5 changes: 5 additions & 0 deletions .changeset/itchy-pianos-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@verdant-web/cli': patch
---

Adds typings for new useWatch option to disable usage tracking
5 changes: 5 additions & 0 deletions .changeset/popular-games-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@verdant-web/react': minor
---

Observe accessed keys of watched entities and only re-render the component when accessed keys change
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"typescript": "^5.4.2"
},
"dependencies": {
"fake-indexeddb": "^5.0.1"
"fake-indexeddb": "^5.0.1",
"prettier": "^3.0.3"
},
"volta": {
"node": "18.17.0"
Expand Down
7 changes: 6 additions & 1 deletion packages/cli/src/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,12 @@ export interface GeneratedHooks<Presence, Profile> {
useSyncStatus: () => boolean;
useWatch<T extends AnyEntity<any, any, any> | null>(
entity: T,
options?: { deep?: boolean },
options?: {
/** Observes changes to all sub-objects */
deep?: boolean,
/** Disables performance enhancements that prevent re-renders if the changed keys aren't used in the component */
untracked?: boolean,
},
): EntityDestructured<T>;
useWatch<T extends EntityFile | null>(
file: T
Expand Down
7 changes: 6 additions & 1 deletion packages/cli/test/__snapshots__/generated.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,12 @@ export interface GeneratedHooks<Presence, Profile> {
useSyncStatus: () => boolean;
useWatch<T extends AnyEntity<any, any, any> | null>(
entity: T,
options?: { deep?: boolean },
options?: {
/** Observes changes to all sub-objects */
deep?: boolean;
/** Disables performance enhancements that prevent re-renders if the changed keys aren't used in the component */
untracked?: boolean;
},
): EntityDestructured<T>;
useWatch<T extends EntityFile | null>(file: T): string | null;
useOnChange<T extends AnyEntity<any, any, any> | null>(
Expand Down
8 changes: 5 additions & 3 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,19 @@
},
"dependencies": {
"@verdant-web/common": "workspace:*",
"suspend-react": "^0.0.8",
"use-sync-external-store": "^1.2.0"
},
"devDependencies": {
"@types/react": "^19.0.1",
"@types/use-sync-external-store": "^0.0.6",
"@verdant-web/store": "workspace:*",
"@vitest/browser": "^2.1.8",
"fake-indexeddb": "^5.0.1",
"jsdom": "^20.0.0",
"playwright": "^1.49.1",
"prettier": "^3.0.3",
"react": "^19.0.0",
"typescript": "^5.4.2",
"vitest": "^2.0.5"
"vitest": "^2.1.8",
"vitest-browser-react": "^0.0.4"
}
}
File renamed without changes.
65 changes: 17 additions & 48 deletions packages/react/src/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@ import {
StorageSchema,
} from '@verdant-web/common';
import {
Query,
SyncTransportMode,
StorageDescriptor,
UserInfo,
Entity,
Client,
ClientWithCollections,
Entity,
EntityFile,
Client,
Query,
QueryStatus,
StorageDescriptor,
SyncTransportMode,
UserInfo,
} from '@verdant-web/store';
import {
ChangeEvent,
createContext,
HTMLAttributes,
HTMLProps,
ReactNode,
use,
useCallback,
useContext,
useEffect,
Expand All @@ -28,16 +28,16 @@ import {
useState,
useSyncExternalStore,
} from 'react';
import { suspend } from 'suspend-react';
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js';
import { useWatch } from './watch.js';

function isQueryCurrentValid(query: Query<any>) {
return !(query.status === 'initial' || query.status === 'initializing');
}
function useLiveQuery(liveQuery: Query<any> | null, disableSuspense = false) {
// suspend if the query doesn't have a valid result set yet.
if (!disableSuspense && liveQuery && !isQueryCurrentValid(liveQuery)) {
suspend(() => liveQuery.resolved, [liveQuery]);
use(liveQuery.resolved);
}
return useSyncExternalStore(
(callback) => {
Expand Down Expand Up @@ -98,40 +98,7 @@ export function createHooks<Presence = any, Profile = any>(
if (!ctx) {
throw new Error('No verdant provider was found');
}
return suspend(() => ctx.readyPromise, ['lofi_' + ctx.namespace]) as any;
}

function useWatch(
liveObject: Entity | EntityFile | null,
options?: { deep?: boolean },
) {
return useSyncExternalStore(
(handler) => {
if (liveObject) {
if ('isFile' in liveObject) {
return liveObject.subscribe('change', handler);
} else {
if (options?.deep) {
return liveObject.subscribe('change', handler);
} else {
return liveObject.subscribe('change', handler);
}
}
}
return () => {};
},
() => {
if (liveObject) {
if (liveObject instanceof EntityFile) {
return liveObject.url;
} else {
return liveObject.getAll();
}
}

return undefined;
},
);
return use(ctx.readyPromise) as ClientWithCollections;
}

function useOnChange(
Expand Down Expand Up @@ -203,7 +170,7 @@ export function createHooks<Presence = any, Profile = any>(
unsubs.forEach((unsub) => unsub());
};
},
() => (peerId ? storage.sync.presence.peers[peerId] ?? null : null),
() => (peerId ? (storage.sync.presence.peers[peerId] ?? null) : null),
);
}

Expand Down Expand Up @@ -418,8 +385,8 @@ export function createHooks<Presence = any, Profile = any>(
fieldSchema.type === 'boolean'
? key
: value === null || value === undefined
? ''
: `${value}`;
? ''
: `${value}`;
const props: HTMLProps<HTMLInputElement> = {
onChange: (e: ChangeEvent<HTMLInputElement>) => {
if (fieldSchema.type === 'number') {
Expand Down Expand Up @@ -598,11 +565,13 @@ export function createHooks<Presence = any, Profile = any>(
value,
children,
sync,
fallback,
...rest
}: {
children?: ReactNode;
value: StorageDescriptor;
sync?: boolean;
fallback?: ReactNode;
}) => {
// auto-open storage when used in provider
useMemo(() => {
Expand Down Expand Up @@ -756,7 +725,7 @@ export function createHooks<Presence = any, Profile = any>(
pageSize,
page: 0,
key: key || getAllPaginatedHookName,
}),
}),
[index, skip, pageSize],
);
const data = useLiveQuery(liveQuery, suspend === false);
Expand Down Expand Up @@ -809,7 +778,7 @@ export function createHooks<Presence = any, Profile = any>(
index,
pageSize,
key: key || getAllInfiniteHookName,
}),
}),
[index, skip, pageSize],
);
const data = useLiveQuery(liveQuery, suspend === false);
Expand Down
Loading

0 comments on commit cacc5b0

Please sign in to comment.