Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
9 changes: 5 additions & 4 deletions src/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,20 @@ test('Batching of subscriptions', async () => {
function A({rep}: {rep: MyRep}) {
const dataA = useSubscribe(
rep,
async tx => (await tx.get('a')) ?? null,
// TODO: Use type param to get when new Replicache is released.
async tx => ((await tx.get('a')) as string | undefined) ?? null,
null,
) as string | null;
);
renderLog.push('render A', dataA);
return <B rep={rep} dataA={dataA} />;
}

function B({rep, dataA}: {rep: MyRep; dataA: string | null}) {
const dataB = useSubscribe(
rep,
async tx => (await tx.get('b')) ?? null,
async tx => ((await tx.get('b')) as string | undefined) ?? null,
null,
) as string | null;
);
renderLog.push('render B', dataA, dataB);
return (
<>
Expand Down
27 changes: 16 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import type {Replicache} from 'replicache';
import type {ReadonlyJSONValue, ReadTransaction} from 'replicache';
import {useEffect, useState} from 'react';
import {unstable_batchedUpdates} from 'react-dom';

type Subscribable = Pick<Replicache, 'subscribe'>;
export type Subscribable<Tx, Data> = {
subscribe: (
query: (tx: Tx) => Promise<Data>,
{onData}: {onData: (data: Data) => void},
) => () => void;
};

// We wrap all the callbacks in a `unstable_batchedUpdates` call to ensure that
// we do not render things more than once over all of the changed subscriptions.
Expand All @@ -22,21 +25,23 @@ function doCallback() {
});
}

export function useSubscribe<R extends ReadonlyJSONValue | undefined>(
rep: Subscribable | null | undefined,
query: (tx: ReadTransaction) => Promise<R>,
export function useSubscribe<Tx, D, R extends D>(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do you really need 3 type params? Doesn't the following work?

export function useSubscribe<Tx, R>(
  r: Subscribable<Tx, R> | null | undefined,
  query: (tx: Tx) => Promise<R>,
  def: R,
  deps: Array<unknown> = [],
): R {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I downloaded this PR and the proposed change works in isolation but it does not work for the tests, and the tests looks right to me. I guess the R extends D is the magic sauce to allow that to work.

r: Subscribable<Tx, D> | null | undefined,
query: (tx: Tx) => Promise<R>,
def: R,
deps: Array<unknown> = [],
): R {
const [snapshot, setSnapshot] = useState<R>(def);
useEffect(() => {
if (!rep) {
if (!r) {
return;
}

const unsubscribe = rep.subscribe(query, {
onData: (data: R) => {
callbacks.push(() => setSnapshot(data));
const unsubscribe = r.subscribe(query, {
onData: data => {
// This is safe because we know that subscribe in fact can only return
// `R` (the return type of query or def).
callbacks.push(() => setSnapshot(data as R));
if (!hasPendingCallback) {
void Promise.resolve().then(doCallback);
hasPendingCallback = true;
Expand All @@ -48,6 +53,6 @@ export function useSubscribe<R extends ReadonlyJSONValue | undefined>(
unsubscribe();
setSnapshot(def);
};
}, [rep, ...deps]);
}, [r, ...deps]);
return snapshot;
}