@evolu/react 8.2.1 → 9.0.0-preview.0
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.
- package/README.md +1 -1
- package/dist/EvoluContext.d.ts +3 -0
- package/dist/EvoluContext.d.ts.map +1 -0
- package/dist/EvoluContext.js +2 -0
- package/dist/EvoluProvider.d.ts +7 -0
- package/dist/EvoluProvider.d.ts.map +1 -0
- package/dist/EvoluProvider.js +4 -0
- package/dist/createUseEvolu.d.ts +13 -0
- package/dist/createUseEvolu.d.ts.map +1 -0
- package/dist/createUseEvolu.js +14 -0
- package/dist/index.d.ts +10 -33
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -42
- package/dist/useAppOwner.d.ts +4 -0
- package/dist/useAppOwner.d.ts.map +1 -0
- package/dist/useAppOwner.js +8 -0
- package/dist/useEvolu.d.ts +9 -0
- package/dist/useEvolu.d.ts.map +1 -0
- package/dist/useEvolu.js +15 -0
- package/dist/useEvoluError.d.ts +4 -0
- package/dist/useEvoluError.d.ts.map +1 -0
- package/dist/useEvoluError.js +8 -0
- package/dist/useQueries.d.ts +9 -0
- package/dist/useQueries.d.ts.map +1 -0
- package/dist/useQueries.js +25 -0
- package/dist/useQuery.d.ts +37 -0
- package/dist/useQuery.d.ts.map +1 -0
- package/dist/useQuery.js +45 -0
- package/dist/useQuerySubscription.d.ts +10 -0
- package/dist/useQuerySubscription.d.ts.map +1 -0
- package/dist/useQuerySubscription.js +18 -0
- package/dist/useSyncState.d.ts +4 -0
- package/dist/useSyncState.d.ts.map +1 -0
- package/dist/useSyncState.js +8 -0
- package/dist/useWasSSR.d.ts +6 -0
- package/dist/useWasSSR.d.ts.map +1 -0
- package/dist/useWasSSR.js +11 -0
- package/package.json +14 -20
- package/src/EvoluContext.ts +4 -0
- package/src/EvoluProvider.tsx +13 -0
- package/src/createUseEvolu.ts +17 -0
- package/src/index.ts +10 -49
- package/src/useAppOwner.ts +13 -0
- package/src/useEvolu.ts +20 -0
- package/src/useEvoluError.ts +9 -0
- package/src/useQueries.ts +46 -0
- package/src/useQuery.ts +55 -0
- package/src/useQuerySubscription.ts +38 -0
- package/src/useSyncState.ts +13 -0
- package/src/useWasSSR.ts +17 -0
package/README.md
CHANGED
|
@@ -12,4 +12,4 @@ The Evolu community is on [GitHub Discussions](https://github.com/evoluhq/evolu/
|
|
|
12
12
|
|
|
13
13
|
To chat with other community members, you can join the [Evolu Discord](https://discord.gg/2J8yyyyxtZ).
|
|
14
14
|
|
|
15
|
-
[](https://twitter.com/evoluhq)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EvoluContext.d.ts","sourceRoot":"","sources":["../src/EvoluContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAG5C,eAAO,MAAM,YAAY,4CAAyC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Evolu } from "@evolu/common/evolu";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
export declare const EvoluProvider: ({ children, value, }: {
|
|
4
|
+
readonly children?: ReactNode | undefined;
|
|
5
|
+
readonly value: Evolu<any>;
|
|
6
|
+
}) => React.ReactElement;
|
|
7
|
+
//# sourceMappingURL=EvoluProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EvoluProvider.d.ts","sourceRoot":"","sources":["../src/EvoluProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGlC,eAAO,MAAM,aAAa,GAAI,sBAG3B;IACD,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;CAC5B,KAAG,KAAK,CAAC,YAAqE,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Evolu, EvoluSchema } from "@evolu/common";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a typed React Hook returning an instance of {@link Evolu}.
|
|
4
|
+
*
|
|
5
|
+
* ### Example
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const useEvolu = createUseEvolu(evolu);
|
|
9
|
+
* const { insert, update } = useEvolu();
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare const createUseEvolu: <S extends EvoluSchema>(evolu: Evolu<S>) => (() => Evolu<S>);
|
|
13
|
+
//# sourceMappingURL=createUseEvolu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createUseEvolu.d.ts","sourceRoot":"","sources":["../src/createUseEvolu.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAGnD;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,WAAW,EAElD,OAAO,KAAK,CAAC,CAAC,CAAC,KACd,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAA+B,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useEvolu } from "./useEvolu.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a typed React Hook returning an instance of {@link Evolu}.
|
|
4
|
+
*
|
|
5
|
+
* ### Example
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const useEvolu = createUseEvolu(evolu);
|
|
9
|
+
* const { insert, update } = useEvolu();
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export const createUseEvolu = (
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
14
|
+
evolu) => useEvolu;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,34 +1,11 @@
|
|
|
1
|
-
export * from "
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* import * as E from "@evolu/react";
|
|
12
|
-
*
|
|
13
|
-
* const TodoId = E.id("Todo");
|
|
14
|
-
* type TodoId = typeof TodoId.Type;
|
|
15
|
-
*
|
|
16
|
-
* const TodoTable = E.table({
|
|
17
|
-
* id: TodoId,
|
|
18
|
-
* title: E.NonEmptyString1000,
|
|
19
|
-
* });
|
|
20
|
-
* type TodoTable = typeof TodoTable.Type;
|
|
21
|
-
*
|
|
22
|
-
* const Database = E.database({
|
|
23
|
-
* todo: TodoTable,
|
|
24
|
-
*
|
|
25
|
-
* // Prefix `_` makes the table local-only (it will not sync)
|
|
26
|
-
* _todo: TodoTable,
|
|
27
|
-
* });
|
|
28
|
-
* type Database = typeof Database.Type;
|
|
29
|
-
*
|
|
30
|
-
* const evolu = E.createEvolu(Database);
|
|
31
|
-
*/
|
|
32
|
-
createEvolu: <T extends import("@evolu/common").EvoluSchema, I>(schema: import("@effect/schema/Schema").Schema<T, I>, config?: Partial<import("@evolu/common").EvoluConfig<T>>) => import("@evolu/common").Evolu<T>;
|
|
33
|
-
export * from "@evolu/common-react";
|
|
1
|
+
export * from "./createUseEvolu.js";
|
|
2
|
+
export * from "./EvoluContext.js";
|
|
3
|
+
export * from "./EvoluProvider.js";
|
|
4
|
+
export * from "./useAppOwner.js";
|
|
5
|
+
export * from "./useEvolu.js";
|
|
6
|
+
export * from "./useEvoluError.js";
|
|
7
|
+
export * from "./useQueries.js";
|
|
8
|
+
export * from "./useQuery.js";
|
|
9
|
+
export * from "./useQuerySubscription.js";
|
|
10
|
+
export * from "./useSyncState.js";
|
|
34
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,42 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export * from "
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export const {
|
|
12
|
-
/**
|
|
13
|
-
* Create Evolu from the database schema.
|
|
14
|
-
*
|
|
15
|
-
* Tables with a name prefixed with `_` are local-only, which means they are
|
|
16
|
-
* never synced. It's useful for device-specific or temporal data.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* import * as S from "@effect/schema/Schema";
|
|
20
|
-
* import * as E from "@evolu/react";
|
|
21
|
-
*
|
|
22
|
-
* const TodoId = E.id("Todo");
|
|
23
|
-
* type TodoId = typeof TodoId.Type;
|
|
24
|
-
*
|
|
25
|
-
* const TodoTable = E.table({
|
|
26
|
-
* id: TodoId,
|
|
27
|
-
* title: E.NonEmptyString1000,
|
|
28
|
-
* });
|
|
29
|
-
* type TodoTable = typeof TodoTable.Type;
|
|
30
|
-
*
|
|
31
|
-
* const Database = E.database({
|
|
32
|
-
* todo: TodoTable,
|
|
33
|
-
*
|
|
34
|
-
* // Prefix `_` makes the table local-only (it will not sync)
|
|
35
|
-
* _todo: TodoTable,
|
|
36
|
-
* });
|
|
37
|
-
* type Database = typeof Database.Type;
|
|
38
|
-
*
|
|
39
|
-
* const evolu = E.createEvolu(Database);
|
|
40
|
-
*/
|
|
41
|
-
createEvolu, } = EvoluFactory.pipe(Effect.provide(EvoluFactoryWebReact), Effect.runSync);
|
|
42
|
-
export * from "@evolu/common-react";
|
|
1
|
+
export * from "./createUseEvolu.js";
|
|
2
|
+
export * from "./EvoluContext.js";
|
|
3
|
+
export * from "./EvoluProvider.js";
|
|
4
|
+
export * from "./useAppOwner.js";
|
|
5
|
+
export * from "./useEvolu.js";
|
|
6
|
+
export * from "./useEvoluError.js";
|
|
7
|
+
export * from "./useQueries.js";
|
|
8
|
+
export * from "./useQuery.js";
|
|
9
|
+
export * from "./useQuerySubscription.js";
|
|
10
|
+
export * from "./useSyncState.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAppOwner.d.ts","sourceRoot":"","sources":["../src/useAppOwner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAa,MAAM,eAAe,CAAC;AAIpD,6CAA6C;AAC7C,eAAO,MAAM,WAAW,QAAO,QAAQ,GAAG,IAOzC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { constNull } from "@evolu/common";
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
import { useEvolu } from "./useEvolu.js";
|
|
4
|
+
/** Subscribe to {@link AppOwner} changes. */
|
|
5
|
+
export const useAppOwner = () => {
|
|
6
|
+
const evolu = useEvolu();
|
|
7
|
+
return useSyncExternalStore(evolu.subscribeAppOwner, evolu.getAppOwner, constNull);
|
|
8
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Evolu } from "@evolu/common/evolu";
|
|
2
|
+
/**
|
|
3
|
+
* React Hook returning a generic instance of {@link Evolu}.
|
|
4
|
+
*
|
|
5
|
+
* This is intended for internal usage. Applications should use
|
|
6
|
+
* {@link createUseEvolu}, which provides a correctly typed instance.
|
|
7
|
+
*/
|
|
8
|
+
export declare const useEvolu: () => Evolu;
|
|
9
|
+
//# sourceMappingURL=useEvolu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useEvolu.d.ts","sourceRoot":"","sources":["../src/useEvolu.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAK5C;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,QAAO,KAQ3B,CAAC"}
|
package/dist/useEvolu.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { EvoluContext } from "./EvoluContext.js";
|
|
3
|
+
/**
|
|
4
|
+
* React Hook returning a generic instance of {@link Evolu}.
|
|
5
|
+
*
|
|
6
|
+
* This is intended for internal usage. Applications should use
|
|
7
|
+
* {@link createUseEvolu}, which provides a correctly typed instance.
|
|
8
|
+
*/
|
|
9
|
+
export const useEvolu = () => {
|
|
10
|
+
const evolu = useContext(EvoluContext);
|
|
11
|
+
if (evolu == null) {
|
|
12
|
+
throw new Error("Could not find Evolu context value. Ensure the component is wrapped in an <EvoluProvider>.");
|
|
13
|
+
}
|
|
14
|
+
return evolu;
|
|
15
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useEvoluError.d.ts","sourceRoot":"","sources":["../src/useEvoluError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,UAAU,EAAE,MAAM,eAAe,CAAC;AAItD,+CAA+C;AAC/C,eAAO,MAAM,aAAa,QAAO,UAAU,GAAG,IAG7C,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { constNull } from "@evolu/common";
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
import { useEvolu } from "./useEvolu.js";
|
|
4
|
+
/** Subscribe to {@link EvoluError} changes. */
|
|
5
|
+
export const useEvoluError = () => {
|
|
6
|
+
const evolu = useEvolu();
|
|
7
|
+
return useSyncExternalStore(evolu.subscribeError, evolu.getError, constNull);
|
|
8
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Queries, QueriesToQueryRows, QueriesToQueryRowsPromises, Row } from "@evolu/common/evolu";
|
|
2
|
+
/** The same as {@link useQuery}, but for many queries. */
|
|
3
|
+
export declare const useQueries: <R extends Row, Q extends Queries<R>, OQ extends Queries<R>>(queries: [...Q], options?: Partial<{
|
|
4
|
+
/** Queries that should be only loaded, not subscribed to. */
|
|
5
|
+
readonly once: [...OQ];
|
|
6
|
+
/** Reuse existing promises instead of loading so query will not suspense. */
|
|
7
|
+
readonly promises: [...QueriesToQueryRowsPromises<Q>, ...QueriesToQueryRowsPromises<OQ>];
|
|
8
|
+
}>) => [...QueriesToQueryRows<Q>, ...QueriesToQueryRows<OQ>];
|
|
9
|
+
//# sourceMappingURL=useQueries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useQueries.d.ts","sourceRoot":"","sources":["../src/useQueries.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,kBAAkB,EAClB,0BAA0B,EAC1B,GAAG,EACJ,MAAM,qBAAqB,CAAC;AAO7B,0DAA0D;AAC1D,eAAO,MAAM,UAAU,GACrB,CAAC,SAAS,GAAG,EACb,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,EACpB,EAAE,SAAS,OAAO,CAAC,CAAC,CAAC,EAErB,SAAS,CAAC,GAAG,CAAC,CAAC,EACf,UAAS,OAAO,CAAC;IACf,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;IAEvB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,CACjB,GAAG,0BAA0B,CAAC,CAAC,CAAC,EAChC,GAAG,0BAA0B,CAAC,EAAE,CAAC,CAClC,CAAC;CACH,CAAM,KACN,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,EAAE,GAAG,kBAAkB,CAAC,EAAE,CAAC,CAgBtD,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { use, useRef } from "react";
|
|
2
|
+
import { useEvolu } from "./useEvolu.js";
|
|
3
|
+
import { useQuerySubscription } from "./useQuerySubscription.js";
|
|
4
|
+
import { useWasSSR } from "./useWasSSR.js";
|
|
5
|
+
/** The same as {@link useQuery}, but for many queries. */
|
|
6
|
+
export const useQueries = (queries, options = {}) => {
|
|
7
|
+
const evolu = useEvolu();
|
|
8
|
+
const once = useRef(options).current.once;
|
|
9
|
+
const allQueries = once ? queries.concat(once) : queries;
|
|
10
|
+
const wasSSR = useWasSSR();
|
|
11
|
+
if (wasSSR) {
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
13
|
+
if (!options.promises)
|
|
14
|
+
evolu.loadQueries(allQueries);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
if (options.promises)
|
|
18
|
+
options.promises.map(use);
|
|
19
|
+
else
|
|
20
|
+
evolu.loadQueries(allQueries).map(use);
|
|
21
|
+
}
|
|
22
|
+
return allQueries.map((query, i) =>
|
|
23
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
24
|
+
useQuerySubscription(query, { once: i > queries.length - 1 }));
|
|
25
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Query, QueryRows, Row } from "@evolu/common/evolu";
|
|
2
|
+
/**
|
|
3
|
+
* Load and subscribe to the Query, and return an object with `rows` and `row`
|
|
4
|
+
* properties that are automatically updated when data changes.
|
|
5
|
+
*
|
|
6
|
+
* Note that {@link useQuery} uses React Suspense. It means every usage of
|
|
7
|
+
* {@link useQuery} blocks rendering until loading is completed. To avoid loading
|
|
8
|
+
* waterfall with more queries, use {@link useQueries}.
|
|
9
|
+
*
|
|
10
|
+
* ### Example
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* // Get all rows.
|
|
14
|
+
* const rows = useQuery(allTodos);
|
|
15
|
+
*
|
|
16
|
+
* // Get the first row (it can be null).
|
|
17
|
+
* const row = useQuery(todoById(1));
|
|
18
|
+
*
|
|
19
|
+
* // Get all rows, but without subscribing to changes.
|
|
20
|
+
* const rows = useQuery(allTodos, { once: true });
|
|
21
|
+
*
|
|
22
|
+
* // Prefetch rows.
|
|
23
|
+
* const allTodos = evolu.createQuery((db) =>
|
|
24
|
+
* db.selectFrom("todo").selectAll(),
|
|
25
|
+
* );
|
|
26
|
+
* const allTodosPromise = evolu.loadQuery(allTodos);
|
|
27
|
+
* // Use prefetched rows.
|
|
28
|
+
* const rows = useQuery(allTodos, { promise: allTodosPromise });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare const useQuery: <R extends Row>(query: Query<R>, options?: Partial<{
|
|
32
|
+
/** Without subscribing to changes. */
|
|
33
|
+
readonly once: boolean;
|
|
34
|
+
/** Reuse existing promise instead of loading so query will not suspense. */
|
|
35
|
+
readonly promise: Promise<QueryRows<R>>;
|
|
36
|
+
}>) => QueryRows<R>;
|
|
37
|
+
//# sourceMappingURL=useQuery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useQuery.d.ts","sourceRoot":"","sources":["../src/useQuery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAO5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,GAAG,EACpC,OAAO,KAAK,CAAC,CAAC,CAAC,EACf,UAAS,OAAO,CAAC;IACf,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAEvB,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;CACzC,CAAM,KACN,SAAS,CAAC,CAAC,CASb,CAAC"}
|
package/dist/useQuery.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { use } from "react";
|
|
2
|
+
import { useEvolu } from "./useEvolu.js";
|
|
3
|
+
import { useQuerySubscription } from "./useQuerySubscription.js";
|
|
4
|
+
import { useWasSSR } from "./useWasSSR.js";
|
|
5
|
+
/**
|
|
6
|
+
* Load and subscribe to the Query, and return an object with `rows` and `row`
|
|
7
|
+
* properties that are automatically updated when data changes.
|
|
8
|
+
*
|
|
9
|
+
* Note that {@link useQuery} uses React Suspense. It means every usage of
|
|
10
|
+
* {@link useQuery} blocks rendering until loading is completed. To avoid loading
|
|
11
|
+
* waterfall with more queries, use {@link useQueries}.
|
|
12
|
+
*
|
|
13
|
+
* ### Example
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* // Get all rows.
|
|
17
|
+
* const rows = useQuery(allTodos);
|
|
18
|
+
*
|
|
19
|
+
* // Get the first row (it can be null).
|
|
20
|
+
* const row = useQuery(todoById(1));
|
|
21
|
+
*
|
|
22
|
+
* // Get all rows, but without subscribing to changes.
|
|
23
|
+
* const rows = useQuery(allTodos, { once: true });
|
|
24
|
+
*
|
|
25
|
+
* // Prefetch rows.
|
|
26
|
+
* const allTodos = evolu.createQuery((db) =>
|
|
27
|
+
* db.selectFrom("todo").selectAll(),
|
|
28
|
+
* );
|
|
29
|
+
* const allTodosPromise = evolu.loadQuery(allTodos);
|
|
30
|
+
* // Use prefetched rows.
|
|
31
|
+
* const rows = useQuery(allTodos, { promise: allTodosPromise });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export const useQuery = (query, options = {}) => {
|
|
35
|
+
const evolu = useEvolu();
|
|
36
|
+
const wasSSR = useWasSSR();
|
|
37
|
+
if (wasSSR) {
|
|
38
|
+
if (!options.promise)
|
|
39
|
+
void evolu.loadQuery(query);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
use(options.promise ?? evolu.loadQuery(query));
|
|
43
|
+
}
|
|
44
|
+
return useQuerySubscription(query, options);
|
|
45
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Query, QueryRows, Row } from "@evolu/common/evolu";
|
|
2
|
+
/** Subscribe to {@link Query} {@link QueryRows} changes. */
|
|
3
|
+
export declare const useQuerySubscription: <R extends Row>(query: Query<R>, options?: Partial<{
|
|
4
|
+
/**
|
|
5
|
+
* Only subscribe and get the current value once. Subscribed query will not
|
|
6
|
+
* invoke React Suspense after a mutation.
|
|
7
|
+
*/
|
|
8
|
+
readonly once: boolean;
|
|
9
|
+
}>) => QueryRows<R>;
|
|
10
|
+
//# sourceMappingURL=useQuerySubscription.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useQuerySubscription.d.ts","sourceRoot":"","sources":["../src/useQuerySubscription.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAa,MAAM,qBAAqB,CAAC;AAIvE,4DAA4D;AAC5D,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS,GAAG,EAChD,OAAO,KAAK,CAAC,CAAC,CAAC,EACf,UAAS,OAAO,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB,CAAM,KACN,SAAS,CAAC,CAAC,CAsBb,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { constVoid } from "@evolu/common";
|
|
2
|
+
import { emptyRows } from "@evolu/common/evolu";
|
|
3
|
+
import { useEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
4
|
+
import { useEvolu } from "./useEvolu.js";
|
|
5
|
+
/** Subscribe to {@link Query} {@link QueryRows} changes. */
|
|
6
|
+
export const useQuerySubscription = (query, options = {}) => {
|
|
7
|
+
const evolu = useEvolu();
|
|
8
|
+
// useRef to not break "rules-of-hooks"
|
|
9
|
+
const { once } = useRef(options).current;
|
|
10
|
+
if (once) {
|
|
11
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
12
|
+
useEffect(
|
|
13
|
+
// No useSyncExternalStore, no unnecessary updates.
|
|
14
|
+
() => evolu.subscribeQuery(query)(constVoid), [evolu, query]);
|
|
15
|
+
return evolu.getQueryRows(query);
|
|
16
|
+
}
|
|
17
|
+
return useSyncExternalStore(useMemo(() => evolu.subscribeQuery(query), [evolu, query]), useMemo(() => () => evolu.getQueryRows(query), [evolu, query]), () => emptyRows);
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSyncState.d.ts","sourceRoot":"","sources":["../src/useSyncState.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAoB,MAAM,qBAAqB,CAAC;AAIlE,8CAA8C;AAC9C,eAAO,MAAM,YAAY,QAAO,SAO/B,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { initialSyncState } from "@evolu/common/evolu";
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
import { useEvolu } from "./useEvolu.js";
|
|
4
|
+
/** Subscribe to {@link SyncState} changes. */
|
|
5
|
+
export const useSyncState = () => {
|
|
6
|
+
const evolu = useEvolu();
|
|
7
|
+
return useSyncExternalStore(evolu.subscribeSyncState, evolu.getSyncState, () => initialSyncState);
|
|
8
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useWasSSR.d.ts","sourceRoot":"","sources":["../src/useWasSSR.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,eAAO,MAAM,SAAS,QAAO,OAO5B,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useRef, useSyncExternalStore } from "react";
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
3
|
+
const emptySubscribe = () => () => { };
|
|
4
|
+
/**
|
|
5
|
+
* "Unlike the typeof window hack, this ensures that the server and hydration
|
|
6
|
+
* sees the same thing."
|
|
7
|
+
*/
|
|
8
|
+
export const useWasSSR = () => {
|
|
9
|
+
const ref = useRef(false);
|
|
10
|
+
return useSyncExternalStore(emptySubscribe, () => ref.current, () => (ref.current = true));
|
|
11
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evolu/react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0-preview.0",
|
|
4
4
|
"description": "Evolu for React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"evolu",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
".": {
|
|
21
21
|
"types": "./dist/index.d.ts",
|
|
22
22
|
"import": "./dist/index.js",
|
|
23
|
-
"browser": "./dist/index.js"
|
|
23
|
+
"browser": "./dist/index.js",
|
|
24
|
+
"react-native": "./dist/index.js"
|
|
24
25
|
}
|
|
25
26
|
},
|
|
26
27
|
"files": [
|
|
@@ -29,35 +30,28 @@
|
|
|
29
30
|
"README.md"
|
|
30
31
|
],
|
|
31
32
|
"devDependencies": {
|
|
32
|
-
"@types/react-dom": "
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"@evolu/
|
|
38
|
-
"@evolu/common-react": "8.1.1",
|
|
39
|
-
"@evolu/common-web": "8.2.2",
|
|
40
|
-
"@evolu/tsconfig": "0.0.2",
|
|
41
|
-
"eslint-config-evolu": "1.0.0"
|
|
33
|
+
"@types/react-dom": "^19.1.6",
|
|
34
|
+
"react": "^19.1.0",
|
|
35
|
+
"typescript": "^5.8.3",
|
|
36
|
+
"vitest": "^3.2.2",
|
|
37
|
+
"@evolu/common": "6.0.0-preview.0",
|
|
38
|
+
"@evolu/tsconfig": "0.0.2"
|
|
42
39
|
},
|
|
43
40
|
"peerDependencies": {
|
|
44
|
-
"@evolu/common": "^
|
|
45
|
-
"
|
|
46
|
-
"@evolu/common-web": "^8.2.2",
|
|
47
|
-
"react-dom": "^18.2.0"
|
|
41
|
+
"@evolu/common": "^6.0.0-preview.0",
|
|
42
|
+
"react": "^19.0.0"
|
|
48
43
|
},
|
|
49
44
|
"publishConfig": {
|
|
50
45
|
"access": "public"
|
|
51
46
|
},
|
|
52
47
|
"engines": {
|
|
53
|
-
"node": ">=
|
|
48
|
+
"node": ">=22.0.0"
|
|
54
49
|
},
|
|
55
50
|
"sideEffects": [],
|
|
56
51
|
"scripts": {
|
|
57
52
|
"dev": "tsc --watch",
|
|
58
|
-
"build": "rm -rf dist && tsc",
|
|
59
|
-
"
|
|
60
|
-
"clean": "rm -rf .turbo node_modules dist",
|
|
53
|
+
"build": "shx rm -rf dist && tsc",
|
|
54
|
+
"clean": "shx rm -rf .turbo node_modules dist",
|
|
61
55
|
"format": "prettier --write \"src/*.{ts,tsx,md}\""
|
|
62
56
|
}
|
|
63
57
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Evolu } from "@evolu/common/evolu";
|
|
4
|
+
import { ReactNode } from "react";
|
|
5
|
+
import { EvoluContext } from "./EvoluContext.js";
|
|
6
|
+
|
|
7
|
+
export const EvoluProvider = ({
|
|
8
|
+
children,
|
|
9
|
+
value,
|
|
10
|
+
}: {
|
|
11
|
+
readonly children?: ReactNode | undefined;
|
|
12
|
+
readonly value: Evolu<any>;
|
|
13
|
+
}): React.ReactElement => <EvoluContext value={value}>{children}</EvoluContext>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Evolu, EvoluSchema } from "@evolu/common";
|
|
2
|
+
import { useEvolu } from "./useEvolu.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a typed React Hook returning an instance of {@link Evolu}.
|
|
6
|
+
*
|
|
7
|
+
* ### Example
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const useEvolu = createUseEvolu(evolu);
|
|
11
|
+
* const { insert, update } = useEvolu();
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export const createUseEvolu = <S extends EvoluSchema>(
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
16
|
+
evolu: Evolu<S>,
|
|
17
|
+
): (() => Evolu<S>) => useEvolu as () => Evolu<S>;
|
package/src/index.ts
CHANGED
|
@@ -1,49 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export * from "
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
// JSDoc doesn't support destructured parameters, so we must copy-paste
|
|
14
|
-
// createEvolu docs from `evolu-common/src/Evolu.ts`.
|
|
15
|
-
// https://github.com/microsoft/TypeScript/issues/11859
|
|
16
|
-
export const {
|
|
17
|
-
/**
|
|
18
|
-
* Create Evolu from the database schema.
|
|
19
|
-
*
|
|
20
|
-
* Tables with a name prefixed with `_` are local-only, which means they are
|
|
21
|
-
* never synced. It's useful for device-specific or temporal data.
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* import * as S from "@effect/schema/Schema";
|
|
25
|
-
* import * as E from "@evolu/react";
|
|
26
|
-
*
|
|
27
|
-
* const TodoId = E.id("Todo");
|
|
28
|
-
* type TodoId = typeof TodoId.Type;
|
|
29
|
-
*
|
|
30
|
-
* const TodoTable = E.table({
|
|
31
|
-
* id: TodoId,
|
|
32
|
-
* title: E.NonEmptyString1000,
|
|
33
|
-
* });
|
|
34
|
-
* type TodoTable = typeof TodoTable.Type;
|
|
35
|
-
*
|
|
36
|
-
* const Database = E.database({
|
|
37
|
-
* todo: TodoTable,
|
|
38
|
-
*
|
|
39
|
-
* // Prefix `_` makes the table local-only (it will not sync)
|
|
40
|
-
* _todo: TodoTable,
|
|
41
|
-
* });
|
|
42
|
-
* type Database = typeof Database.Type;
|
|
43
|
-
*
|
|
44
|
-
* const evolu = E.createEvolu(Database);
|
|
45
|
-
*/
|
|
46
|
-
createEvolu,
|
|
47
|
-
} = EvoluFactory.pipe(Effect.provide(EvoluFactoryWebReact), Effect.runSync);
|
|
48
|
-
|
|
49
|
-
export * from "@evolu/common-react";
|
|
1
|
+
export * from "./createUseEvolu.js";
|
|
2
|
+
export * from "./EvoluContext.js";
|
|
3
|
+
export * from "./EvoluProvider.js";
|
|
4
|
+
export * from "./useAppOwner.js";
|
|
5
|
+
export * from "./useEvolu.js";
|
|
6
|
+
export * from "./useEvoluError.js";
|
|
7
|
+
export * from "./useQueries.js";
|
|
8
|
+
export * from "./useQuery.js";
|
|
9
|
+
export * from "./useQuerySubscription.js";
|
|
10
|
+
export * from "./useSyncState.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AppOwner, constNull } from "@evolu/common";
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
import { useEvolu } from "./useEvolu.js";
|
|
4
|
+
|
|
5
|
+
/** Subscribe to {@link AppOwner} changes. */
|
|
6
|
+
export const useAppOwner = (): AppOwner | null => {
|
|
7
|
+
const evolu = useEvolu();
|
|
8
|
+
return useSyncExternalStore(
|
|
9
|
+
evolu.subscribeAppOwner,
|
|
10
|
+
evolu.getAppOwner,
|
|
11
|
+
constNull,
|
|
12
|
+
);
|
|
13
|
+
};
|
package/src/useEvolu.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Evolu } from "@evolu/common/evolu";
|
|
2
|
+
import { useContext } from "react";
|
|
3
|
+
import { EvoluContext } from "./EvoluContext.js";
|
|
4
|
+
import type { createUseEvolu } from "./createUseEvolu.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* React Hook returning a generic instance of {@link Evolu}.
|
|
8
|
+
*
|
|
9
|
+
* This is intended for internal usage. Applications should use
|
|
10
|
+
* {@link createUseEvolu}, which provides a correctly typed instance.
|
|
11
|
+
*/
|
|
12
|
+
export const useEvolu = (): Evolu => {
|
|
13
|
+
const evolu = useContext(EvoluContext);
|
|
14
|
+
if (evolu == null) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"Could not find Evolu context value. Ensure the component is wrapped in an <EvoluProvider>.",
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return evolu as Evolu;
|
|
20
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { constNull, EvoluError } from "@evolu/common";
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
import { useEvolu } from "./useEvolu.js";
|
|
4
|
+
|
|
5
|
+
/** Subscribe to {@link EvoluError} changes. */
|
|
6
|
+
export const useEvoluError = (): EvoluError | null => {
|
|
7
|
+
const evolu = useEvolu();
|
|
8
|
+
return useSyncExternalStore(evolu.subscribeError, evolu.getError, constNull);
|
|
9
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Queries,
|
|
3
|
+
QueriesToQueryRows,
|
|
4
|
+
QueriesToQueryRowsPromises,
|
|
5
|
+
Row,
|
|
6
|
+
} from "@evolu/common/evolu";
|
|
7
|
+
import { use, useRef } from "react";
|
|
8
|
+
import { useEvolu } from "./useEvolu.js";
|
|
9
|
+
import type { useQuery } from "./useQuery.js";
|
|
10
|
+
import { useQuerySubscription } from "./useQuerySubscription.js";
|
|
11
|
+
import { useWasSSR } from "./useWasSSR.js";
|
|
12
|
+
|
|
13
|
+
/** The same as {@link useQuery}, but for many queries. */
|
|
14
|
+
export const useQueries = <
|
|
15
|
+
R extends Row,
|
|
16
|
+
Q extends Queries<R>,
|
|
17
|
+
OQ extends Queries<R>,
|
|
18
|
+
>(
|
|
19
|
+
queries: [...Q],
|
|
20
|
+
options: Partial<{
|
|
21
|
+
/** Queries that should be only loaded, not subscribed to. */
|
|
22
|
+
readonly once: [...OQ];
|
|
23
|
+
|
|
24
|
+
/** Reuse existing promises instead of loading so query will not suspense. */
|
|
25
|
+
readonly promises: [
|
|
26
|
+
...QueriesToQueryRowsPromises<Q>,
|
|
27
|
+
...QueriesToQueryRowsPromises<OQ>,
|
|
28
|
+
];
|
|
29
|
+
}> = {},
|
|
30
|
+
): [...QueriesToQueryRows<Q>, ...QueriesToQueryRows<OQ>] => {
|
|
31
|
+
const evolu = useEvolu();
|
|
32
|
+
const once = useRef(options).current.once;
|
|
33
|
+
const allQueries = once ? queries.concat(once) : queries;
|
|
34
|
+
const wasSSR = useWasSSR();
|
|
35
|
+
if (wasSSR) {
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
37
|
+
if (!options.promises) evolu.loadQueries(allQueries);
|
|
38
|
+
} else {
|
|
39
|
+
if (options.promises) options.promises.map(use);
|
|
40
|
+
else evolu.loadQueries(allQueries).map(use);
|
|
41
|
+
}
|
|
42
|
+
return allQueries.map((query, i) =>
|
|
43
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
44
|
+
useQuerySubscription(query, { once: i > queries.length - 1 }),
|
|
45
|
+
) as never;
|
|
46
|
+
};
|
package/src/useQuery.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Query, QueryRows, Row } from "@evolu/common/evolu";
|
|
2
|
+
import { use } from "react";
|
|
3
|
+
import { useEvolu } from "./useEvolu.js";
|
|
4
|
+
import type { useQueries } from "./useQueries.js";
|
|
5
|
+
import { useQuerySubscription } from "./useQuerySubscription.js";
|
|
6
|
+
import { useWasSSR } from "./useWasSSR.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Load and subscribe to the Query, and return an object with `rows` and `row`
|
|
10
|
+
* properties that are automatically updated when data changes.
|
|
11
|
+
*
|
|
12
|
+
* Note that {@link useQuery} uses React Suspense. It means every usage of
|
|
13
|
+
* {@link useQuery} blocks rendering until loading is completed. To avoid loading
|
|
14
|
+
* waterfall with more queries, use {@link useQueries}.
|
|
15
|
+
*
|
|
16
|
+
* ### Example
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* // Get all rows.
|
|
20
|
+
* const rows = useQuery(allTodos);
|
|
21
|
+
*
|
|
22
|
+
* // Get the first row (it can be null).
|
|
23
|
+
* const row = useQuery(todoById(1));
|
|
24
|
+
*
|
|
25
|
+
* // Get all rows, but without subscribing to changes.
|
|
26
|
+
* const rows = useQuery(allTodos, { once: true });
|
|
27
|
+
*
|
|
28
|
+
* // Prefetch rows.
|
|
29
|
+
* const allTodos = evolu.createQuery((db) =>
|
|
30
|
+
* db.selectFrom("todo").selectAll(),
|
|
31
|
+
* );
|
|
32
|
+
* const allTodosPromise = evolu.loadQuery(allTodos);
|
|
33
|
+
* // Use prefetched rows.
|
|
34
|
+
* const rows = useQuery(allTodos, { promise: allTodosPromise });
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export const useQuery = <R extends Row>(
|
|
38
|
+
query: Query<R>,
|
|
39
|
+
options: Partial<{
|
|
40
|
+
/** Without subscribing to changes. */
|
|
41
|
+
readonly once: boolean;
|
|
42
|
+
|
|
43
|
+
/** Reuse existing promise instead of loading so query will not suspense. */
|
|
44
|
+
readonly promise: Promise<QueryRows<R>>;
|
|
45
|
+
}> = {},
|
|
46
|
+
): QueryRows<R> => {
|
|
47
|
+
const evolu = useEvolu();
|
|
48
|
+
const wasSSR = useWasSSR();
|
|
49
|
+
if (wasSSR) {
|
|
50
|
+
if (!options.promise) void evolu.loadQuery(query);
|
|
51
|
+
} else {
|
|
52
|
+
use(options.promise ?? evolu.loadQuery(query));
|
|
53
|
+
}
|
|
54
|
+
return useQuerySubscription(query, options);
|
|
55
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { constVoid } from "@evolu/common";
|
|
2
|
+
import { Query, QueryRows, Row, emptyRows } from "@evolu/common/evolu";
|
|
3
|
+
import { useEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
4
|
+
import { useEvolu } from "./useEvolu.js";
|
|
5
|
+
|
|
6
|
+
/** Subscribe to {@link Query} {@link QueryRows} changes. */
|
|
7
|
+
export const useQuerySubscription = <R extends Row>(
|
|
8
|
+
query: Query<R>,
|
|
9
|
+
options: Partial<{
|
|
10
|
+
/**
|
|
11
|
+
* Only subscribe and get the current value once. Subscribed query will not
|
|
12
|
+
* invoke React Suspense after a mutation.
|
|
13
|
+
*/
|
|
14
|
+
readonly once: boolean;
|
|
15
|
+
}> = {},
|
|
16
|
+
): QueryRows<R> => {
|
|
17
|
+
const evolu = useEvolu();
|
|
18
|
+
|
|
19
|
+
// useRef to not break "rules-of-hooks"
|
|
20
|
+
const { once } = useRef(options).current;
|
|
21
|
+
|
|
22
|
+
if (once) {
|
|
23
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
24
|
+
useEffect(
|
|
25
|
+
// No useSyncExternalStore, no unnecessary updates.
|
|
26
|
+
() => evolu.subscribeQuery(query)(constVoid),
|
|
27
|
+
[evolu, query],
|
|
28
|
+
);
|
|
29
|
+
return evolu.getQueryRows(query);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return useSyncExternalStore(
|
|
33
|
+
useMemo(() => evolu.subscribeQuery(query), [evolu, query]),
|
|
34
|
+
useMemo(() => () => evolu.getQueryRows(query), [evolu, query]),
|
|
35
|
+
() => emptyRows as QueryRows<R>,
|
|
36
|
+
/* eslint-enable react-hooks/rules-of-hooks */
|
|
37
|
+
);
|
|
38
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SyncState, initialSyncState } from "@evolu/common/evolu";
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
import { useEvolu } from "./useEvolu.js";
|
|
4
|
+
|
|
5
|
+
/** Subscribe to {@link SyncState} changes. */
|
|
6
|
+
export const useSyncState = (): SyncState => {
|
|
7
|
+
const evolu = useEvolu();
|
|
8
|
+
return useSyncExternalStore(
|
|
9
|
+
evolu.subscribeSyncState,
|
|
10
|
+
evolu.getSyncState,
|
|
11
|
+
() => initialSyncState,
|
|
12
|
+
);
|
|
13
|
+
};
|
package/src/useWasSSR.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useRef, useSyncExternalStore } from "react";
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
4
|
+
const emptySubscribe = () => () => {};
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* "Unlike the typeof window hack, this ensures that the server and hydration
|
|
8
|
+
* sees the same thing."
|
|
9
|
+
*/
|
|
10
|
+
export const useWasSSR = (): boolean => {
|
|
11
|
+
const ref = useRef(false);
|
|
12
|
+
return useSyncExternalStore(
|
|
13
|
+
emptySubscribe,
|
|
14
|
+
() => ref.current,
|
|
15
|
+
() => (ref.current = true),
|
|
16
|
+
);
|
|
17
|
+
};
|