Skip to content

Commit

Permalink
Improving EvoluCommonReact API
Browse files Browse the repository at this point in the history
- React use polyfill
- useQuerySubscription once
- useQuery once
  • Loading branch information
steida committed Nov 21, 2023
1 parent 95d4a96 commit 531bb0c
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 80 deletions.
20 changes: 2 additions & 18 deletions apps/web/components/NextJsExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ const {
useUpdate,
useOwner,
useEvolu,
// useQuerySubscription,
// use,
// useQueries,
} = Evolu.create(Database, {
reloadUrl: "/examples/nextjs",
Expand All @@ -68,24 +70,6 @@ const {

export const NextJsExample: FC = () => {
const [todosShown, setTodosShown] = useState(true);
// const evolu = useEvolu();

// const todoId = createQuery((db) => db.selectFrom("todo").select(["id"]));

// evolu.loadQueries([todoId, todosWithCategories]).then(([a, b]) => {
// //
// });

// const [a, b, c, d, e] = useQueries(
// [todoId, todosWithCategories, todoCategories],
// [todoCategories],
// [todoId],
// );

// a.rows[0].

// // eslint-disable-next-line no-console
// console.log(a, b, c, d, e);

// https://react.dev/reference/react/useTransition#building-a-suspense-enabled-router
const handleTabClick = (): void =>
Expand Down
110 changes: 56 additions & 54 deletions packages/evolu-common-react/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,34 @@ import {
Evolu,
EvoluError,
Owner,
PlatformName,
Queries,
Query,
QueryResult,
QueryResultsFromQueries,
Row,
Schema,
SyncState,
emptyRows,
queryResultFromRows,
} from "@evolu/common";
import { Context, Effect, Function, Layer } from "effect";
import ReactExports, {
FC,
ReactNode,
Usable,
createContext,
useContext,
useEffect,
useMemo,
useRef,
useSyncExternalStore,
} from "react";

export interface EvoluCommonReact<S extends Schema = Schema> {
/** TODO: Docs */
readonly evolu: Evolu<S>;

/** A React 19 `use` polyfill. */
readonly use: <T>(usable: Usable<T>) => T;

/** TODO: Docs */
readonly useEvolu: () => Evolu<S>;

Expand All @@ -39,38 +41,46 @@ export interface EvoluCommonReact<S extends Schema = Schema> {
readonly createQuery: Evolu<S>["createQuery"];

/**
* It's like React `use` Hook but for React 18. It uses React `use` with React 19.
* TODO: Docs
* Loading promises are released on mutation by default, so loading the same
* query will be suspended again, which is undesirable if we already have such
* a query on a page. Luckily, subscribeQuery tracks subscribed queries to be
* automatically updated on mutation while unsubscribed queries are released.
*/
readonly useQueryPromise: <R extends Row>(
promise: Promise<QueryResult<R>>,
) => QueryResult<R>;

/** TODO: Docs */
readonly useQuerySubscription: <R extends Row>(
query: Query<R>,
options?: Partial<{
/** TODO: Docs, exaplain why once is useful. */
readonly once: boolean;
}>,
) => QueryResult<R>;

/** TODO: Docs */
readonly useQuery: <R extends Row>(query: Query<R>) => QueryResult<R>;

/** TODO: Docs */
readonly useQueryOnce: <R extends Row>(query: Query<R>) => QueryResult<R>;
readonly useQuery: <R extends Row>(
query: Query<R>,
options?: Partial<{
readonly once: boolean;
}>,
) => QueryResult<R>;

/** TODO: Docs */
/**
* TODO: Docs
* For more than one query, always use useQueries Hook to avoid loading waterfalls
* and to cache loading promises.
* This is possible of course:
* const foo = use(useEvolu().loadQuery(todos)()
* but it will not cache loading promise nor subscribe updates.
* That's why we have useQuery and useQueries.
*
*/
readonly useQueries: <
R extends Row,
Q1 extends Queries<R>,
Q2 extends Queries<R>,
Q3 extends Queries<R>,
>(
queries: [...Q1],
loadOnlyQueries?: [...Q2],
subscribeOnlyQueries?: [...Q3],
) => [
...QueryResultsFromQueries<Q1>,
...QueryResultsFromQueries<Q2>,
...QueryResultsFromQueries<Q3>,
];
) => [...QueryResultsFromQueries<Q1>, ...QueryResultsFromQueries<Q2>];

/** TODO: Docs */
readonly useCreate: () => Evolu<S>["create"];
Expand Down Expand Up @@ -102,33 +112,40 @@ export const EvoluCommonReact = Context.Tag<EvoluCommonReact>();
export const EvoluCommonReactLive = Layer.effect(
EvoluCommonReact,
Effect.gen(function* (_) {
const platformName = yield* _(PlatformName);
const evolu = yield* _(Evolu);
const EvoluContext = createContext<Evolu>(evolu);

const useEvolu: EvoluCommonReact["useEvolu"] = () =>
useContext(EvoluContext);

// TODO: Accept also Promise<ReadonlyArray<QueryResult<R>>>
const useQueryPromise = <R extends Row>(
promise: Promise<QueryResult<R>>,
): QueryResult<R> =>
platformName === "server"
? queryResultFromRows(emptyRows<R>())
: use(promise);

const useQuerySubscription = <R extends Row>(
query: Query<R>,
): QueryResult<R> => {
const useQuerySubscription: EvoluCommonReact["useQuerySubscription"] = (
query,
options = {},
) => {
const evolu = useEvolu();
// The options can't be change, hence useRef.
const optionsRef = useRef(options).current;

/* eslint-disable react-hooks/rules-of-hooks */
if (optionsRef.once) {
// No useSyncExternalStore, no unnecessary updates.
useEffect(
() => evolu.subscribeQuery(query)(Function.constVoid),
[evolu, query],
);
return evolu.getQuery(query);
}

return useSyncExternalStore(
useMemo(() => evolu.subscribeQuery(query), [evolu, query]),
useMemo(() => () => evolu.getQuery(query), [evolu, query]),
);
/* eslint-enable react-hooks/rules-of-hooks */
};

return EvoluCommonReact.of({
evolu,
use,
useEvolu,

useEvoluError: () => {
Expand All @@ -142,29 +159,14 @@ export const EvoluCommonReactLive = Layer.effect(

createQuery: evolu.createQuery,
useQuerySubscription,
useQueryPromise,

useQuery: (query) => {
const evolu = useEvolu();
useQueryPromise(evolu.loadQuery(query));
return useQuerySubscription(query);
},

useQueryOnce: (query) => {
useQuery: (query, options) => {
const evolu = useEvolu();
const result = useQueryPromise(evolu.loadQuery(query));
// Loading promises are released on mutation by default, so loading the same
// query will be suspended again, which is undesirable if we already have such
// a query on a page. Luckily, subscribeQuery tracks subscribed queries to be
// automatically updated on mutation while unsubscribed queries are released.
useEffect(
() => evolu.subscribeQuery(query)(Function.constVoid),
[evolu, query],
);
return result;
use(evolu.loadQuery(query));
return useQuerySubscription(query, options);
},

useQueries: (_queries, _loadOnlyQueries, _subscribeOnlyQueries) => {
useQueries: (_queries, _loadOnlyQueries) => {
// const evolu = useEvolu();
// const promise = evolu.loadQueries(
// queries.concat(loadOnlyQueries || []),
Expand All @@ -174,8 +176,8 @@ export const EvoluCommonReactLive = Layer.effect(
throw "";
},

useCreate: () => useContext(EvoluContext).create,
useUpdate: () => useContext(EvoluContext).update,
useCreate: () => useEvolu().create,
useUpdate: () => useEvolu().update,

useOwner: () => {
const evolu = useEvolu();
Expand Down
13 changes: 5 additions & 8 deletions packages/evolu-react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as S from "@effect/schema/Schema";
import { Config, ConfigLive, Schema } from "@evolu/common";
import { EvoluCommonReact, EvoluCommonReactLive } from "@evolu/common-react";
import { EvoluCommonWebLive, PlatformNameLive } from "@evolu/common-web";
import { Effect, Layer } from "effect";
import { EvoluCommonWebLive } from "@evolu/common-web";
import { Effect } from "effect";

export * from "@evolu/common/public";

Expand All @@ -15,12 +15,9 @@ export const create = <From, To extends Schema>(
): EvoluCommonReact<To> => {
if (!fastRefreshRef)
fastRefreshRef = EvoluCommonReact.pipe(
Effect.provide(
EvoluCommonReactLive.pipe(
Layer.use(Layer.merge(EvoluCommonWebLive, PlatformNameLive)),
Layer.use(ConfigLive(config)),
),
),
Effect.provide(EvoluCommonReactLive),
Effect.provide(EvoluCommonWebLive),
Effect.provide(ConfigLive(config)),
Effect.runSync,
);
fastRefreshRef.evolu.ensureSchema(schema);
Expand Down

0 comments on commit 531bb0c

Please sign in to comment.