Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: updated with solidjs and feedback
  • Loading branch information
0xcadams committed Jul 22, 2025
commit 6d5be78e95cc6eb38772d505e9b1329d77b299cf
12 changes: 2 additions & 10 deletions apps/zbugs/src/pages/issue/issue-composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import {
MAX_ISSUE_TITLE_LENGTH,
} from '../../limits.ts';
import {isCtrlEnter} from './is-ctrl-enter.ts';
import {promiseRace} from '../../../../../packages/shared/src/promise.ts';
import {sleep} from '../../../../../packages/shared/src/sleep.ts';

interface Props {
/** If id is defined the issue created by the composer. */
Expand Down Expand Up @@ -51,10 +49,10 @@ export function IssueComposer({isOpen, onDismiss}: Props) {
});
}, [description]);

const handleSubmit = async () => {
const handleSubmit = () => {
const id = nanoid();

const result = z.mutate.issue.create({
z.mutate.issue.create({
id,
title,
description: description ?? '',
Expand All @@ -64,12 +62,6 @@ export function IssueComposer({isOpen, onDismiss}: Props) {

reset();
onDismiss(id);

const raceResult = await promiseRace([sleep(5000), result.server]);
if (raceResult === 0) {
// TODO show toast
console.log('timed out');
}
};

const reset = () => {
Expand Down
9 changes: 3 additions & 6 deletions apps/zbugs/src/zero-init.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {ZeroProvider} from '@rocicorp/zero/react';
import {useLogin} from './hooks/use-login.tsx';
import {createMutators} from '../shared/mutators.ts';
import {createMutators, type Mutators} from '../shared/mutators.ts';
import {useMemo, type ReactNode} from 'react';
import {schema} from '../shared/schema.ts';
import {schema, type Schema} from '../shared/schema.ts';
import type {ZeroOptions} from '@rocicorp/zero';

export function ZeroInit({children}: {children: ReactNode}) {
Expand All @@ -22,10 +22,7 @@ export function ZeroInit({children}: {children: ReactNode}) {
}
return login.loginState?.encoded;
},
} as const satisfies ZeroOptions<
typeof schema,
ReturnType<typeof createMutators>
>;
} as const satisfies ZeroOptions<Schema, Mutators>;
}, [login]);

return <ZeroProvider {...props}>{children}</ZeroProvider>;
Expand Down
6 changes: 0 additions & 6 deletions packages/shared/src/promise.ts

This file was deleted.

6 changes: 6 additions & 0 deletions packages/zero-client/src/client/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ export interface ZeroOptions<

/**
* `onOnlineChange` is called when the Zero instance's online status changes.
*
* @deprecated Use `onOnline` on the Zero instance instead. e.g.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nuts. @grgbkr had gone to a lot of trouble to pass in all the configuration to Zero. But I think didn't consider this problem/use-case. So maybe passing in all configuration cannot work.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can support both here. The adding/removal of listeners doesn't need to be all up-front. And this doesn't need to be deprecated.

Or, if it's better, we can move this all to the React/Solid layers. And continue to use onOnlineChange and track the online state with React Context.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it will be very convoluted to track the listeners state in the React/Solid layers. I'm ok with this change.

The other to event listener type configs we currently have are onUpdateNeeded and onClientStateNotFound. Possibly these should move on to the Zero instance as well, and our general pattern would be event listeners on the Zero instance. I think these are unlikely to need multiple listeners like onOnline might, but it may just be more ergonomic to have them on Zero given React hooks. I think it would be good to build a UI for onUpdateNeeded to figure out what is most ergonomic.

* ```ts
* const zero = new Zero({...});
* zero.onOnline((online) => { ... });
* ```
*/
onOnlineChange?: ((online: boolean) => void) | undefined;

Expand Down
18 changes: 10 additions & 8 deletions packages/zero-client/src/client/zero.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
ReplicacheImpl,
type ReplicacheImplOptions,
} from '../../../replicache/src/impl.ts';
import {promiseRace} from '../../../shared/src/promise.ts';
import {dropDatabase} from '../../../replicache/src/persist/collect-idb-databases.ts';
import type {Puller, PullerResult} from '../../../replicache/src/puller.ts';
import type {Pusher, PusherResult} from '../../../replicache/src/pusher.ts';
Expand Down Expand Up @@ -903,8 +902,7 @@ export class Zero<

lc.debug?.('Closing Zero instance. Stack:', new Error().stack);

// should we unsubscribe all listeners here?
// this.#onlineManager.cleanup();
this.#onlineManager.cleanup();

if (this.#connectionState !== ConnectionState.Disconnected) {
this.#disconnect(
Expand Down Expand Up @@ -1802,10 +1800,6 @@ export class Zero<
/**
* A rough heuristic for whether the client is currently online and
* authenticated.
*
* This does *not* update in React when the online status changes.
* Use {@link useZeroOnline} or {@link addOnlineChangeListener} to subscribe
* to online status changes.
*/
get online(): boolean {
return this.#onlineManager.online;
Expand All @@ -1815,10 +1809,11 @@ export class Zero<
* Subscribe to online status changes.
*
* This is useful when you want to update state based on the online status.
*
* @param listener - The listener to subscribe to.
* @returns A function to unsubscribe the listener.
*/
addOnlineChangeListener = (
onOnline = (
listener: (online: boolean) => void,
): (() => void) => this.#onlineManager.subscribe(listener);

Expand Down Expand Up @@ -2109,6 +2104,13 @@ function addWebSocketIDToLogContext(
return lc.withContext('wsid', wsid);
}

/**
* Like Promise.race but returns the index of the first promise that resolved.
*/
function promiseRace(ps: Promise<unknown>[]): Promise<number> {
return Promise.race(ps.map((p, i) => p.then(() => i)));
}

class TimedOutError extends Error {
constructor(m: string) {
super(`${m} timed out`);
Expand Down
3 changes: 2 additions & 1 deletion packages/zero-react/src/use-zero-online.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import {useZero} from './zero-provider.tsx';
* Hook to subscribe to the online status of the Zero instance.
*
* This is useful when you want to update state based on the online status.
*
* @returns The online status of the Zero instance.
*/
export function useZeroOnline(): boolean {
const zero = useZero();
return useSyncExternalStore(
zero.addOnlineChangeListener,
zero.onOnline,
() => zero.online,
() => zero.online,
);
Expand Down
1 change: 1 addition & 0 deletions packages/zero-solid/src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export {
useQuery,
} from './use-query.ts';
export {createUseZero, createZero, useZero, ZeroProvider} from './use-zero.ts';
export {useZeroOnline} from './use-zero-online.ts';
26 changes: 26 additions & 0 deletions packages/zero-solid/src/use-zero-online.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {createSignal, onCleanup, type Accessor} from 'solid-js';
import {useZero} from './use-zero.ts';

/**
* Tracks the online status of the current Zero instance.
*
* @returns An accessor — call `online()` to get a reactive `boolean`.
*
* @example
* const online = useZeroOnline();
*
* <span>
* {online() ? 'Online' : 'Offline'}
* </span>
*/
export function useZeroOnline(): Accessor<boolean> {
const zero = useZero()();

const [online, setOnline] = createSignal<boolean>(zero?.online ?? false);

const unsubscribe = zero?.onOnline(setOnline);

onCleanup(unsubscribe ?? (() => {}));

return online;
}
Loading