fix client imports to prevent multiple react context's

This commit is contained in:
dswbx
2025-11-25 16:16:10 +01:00
parent 5e5dc62304
commit 0cf1d86213
25 changed files with 59 additions and 49 deletions

View File

@@ -186,6 +186,8 @@ async function buildUiElements() {
outDir: "dist/ui/elements", outDir: "dist/ui/elements",
external: [ external: [
"ui/client", "ui/client",
"bknd",
/^bknd\/.*/,
"react", "react",
"react-dom", "react-dom",
"react/jsx-runtime", "react/jsx-runtime",

View File

@@ -9,7 +9,7 @@ import {
useState, useState,
type ReactNode, type ReactNode,
} from "react"; } from "react";
import { useApi } from "ui/client"; import { useApi } from "bknd/client";
import { type TSchemaActions, getSchemaActions } from "./schema/actions"; import { type TSchemaActions, getSchemaActions } from "./schema/actions";
import { AppReduced } from "./utils/AppReduced"; import { AppReduced } from "./utils/AppReduced";
import { Message } from "ui/components/display/Message"; import { Message } from "ui/components/display/Message";

View File

@@ -14,18 +14,20 @@ const ClientContext = createContext<BkndClientContext>(undefined!);
export type ClientProviderProps = { export type ClientProviderProps = {
children?: ReactNode; children?: ReactNode;
baseUrl?: string; baseUrl?: string;
api?: Api;
} & ApiOptions; } & ApiOptions;
export const ClientProvider = ({ export const ClientProvider = ({
children, children,
host, host,
baseUrl: _baseUrl = host, baseUrl: _baseUrl = host,
api: _api,
...props ...props
}: ClientProviderProps) => { }: ClientProviderProps) => {
const winCtx = useBkndWindowContext(); const winCtx = useBkndWindowContext();
const _ctx = useClientContext(); const _ctx = useClientContext();
let actualBaseUrl = _baseUrl ?? _ctx?.baseUrl ?? ""; let actualBaseUrl = _baseUrl ?? _ctx?.baseUrl ?? "";
let user: any = undefined; let user: any;
if (winCtx) { if (winCtx) {
user = winCtx.user; user = winCtx.user;
@@ -40,6 +42,7 @@ export const ClientProvider = ({
const apiProps = { user, ...props, host: actualBaseUrl }; const apiProps = { user, ...props, host: actualBaseUrl };
const api = useMemo( const api = useMemo(
() => () =>
_api ??
new Api({ new Api({
...apiProps, ...apiProps,
verbose: isDebug(), verbose: isDebug(),
@@ -50,7 +53,7 @@ export const ClientProvider = ({
} }
}, },
}), }),
[JSON.stringify(apiProps)], [_api, JSON.stringify(apiProps)],
); );
const [authState, setAuthState] = useState<Partial<AuthState> | undefined>(api.getAuthState()); const [authState, setAuthState] = useState<Partial<AuthState> | undefined>(api.getAuthState());
@@ -64,6 +67,10 @@ export const ClientProvider = ({
export const useApi = (host?: ApiOptions["host"]): Api => { export const useApi = (host?: ApiOptions["host"]): Api => {
const context = useContext(ClientContext); const context = useContext(ClientContext);
if (!context) {
throw new Error("useApi must be used within a ClientProvider");
}
if (!context?.api || (host && host.length > 0 && host !== context.baseUrl)) { if (!context?.api || (host && host.length > 0 && host !== context.baseUrl)) {
return new Api({ host: host ?? "" }); return new Api({ host: host ?? "" });
} }

View File

@@ -2,7 +2,7 @@ import type { Api } from "Api";
import { FetchPromise, type ModuleApi, type ResponseObject } from "modules/ModuleApi"; import { FetchPromise, type ModuleApi, type ResponseObject } from "modules/ModuleApi";
import useSWR, { type SWRConfiguration, useSWRConfig, type Middleware, type SWRHook } from "swr"; import useSWR, { type SWRConfiguration, useSWRConfig, type Middleware, type SWRHook } from "swr";
import useSWRInfinite from "swr/infinite"; import useSWRInfinite from "swr/infinite";
import { useApi } from "ui/client"; import { useApi } from "../ClientProvider";
import { useState } from "react"; import { useState } from "react";
export const useApiQuery = < export const useApiQuery = <

View File

@@ -10,7 +10,7 @@ import type {
import { objectTransform, encodeSearch } from "bknd/utils"; import { objectTransform, encodeSearch } from "bknd/utils";
import type { Insertable, Selectable, Updateable, Generated } from "kysely"; import type { Insertable, Selectable, Updateable, Generated } from "kysely";
import useSWR, { type SWRConfiguration, type SWRResponse, mutate } from "swr"; import useSWR, { type SWRConfiguration, type SWRResponse, mutate } from "swr";
import { type Api, useApi } from "ui/client"; import { type Api, useApi } from "bknd/client";
export class UseEntityApiError<Payload = any> extends Error { export class UseEntityApiError<Payload = any> extends Error {
constructor( constructor(

View File

@@ -4,6 +4,7 @@ export {
type ClientProviderProps, type ClientProviderProps,
useApi, useApi,
useBaseUrl, useBaseUrl,
useClientContext
} from "./ClientProvider"; } from "./ClientProvider";
export * from "./api/use-api"; export * from "./api/use-api";

View File

@@ -1,7 +1,6 @@
import type { AuthState } from "Api"; import type { AuthState } from "Api";
import type { AuthResponse } from "bknd"; import type { AuthResponse } from "bknd";
import { useApi, useInvalidate } from "ui/client"; import { useApi, useInvalidate, useClientContext } from "bknd/client";
import { useClientContext } from "ui/client/ClientProvider";
type LoginData = { type LoginData = {
email: string; email: string;

View File

@@ -1,9 +1,11 @@
import type { AppAuthSchema } from "auth/auth-schema"; import type { AppAuthSchema } from "auth/auth-schema";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useApi } from "ui/client"; import { useApi } from "bknd/client";
type AuthStrategyData = Pick<AppAuthSchema, "strategies" | "basepath">; type AuthStrategyData = Pick<AppAuthSchema, "strategies" | "basepath">;
export const useAuthStrategies = (options?: { baseUrl?: string }): Partial<AuthStrategyData> & { export const useAuthStrategies = (options?: {
baseUrl?: string;
}): Partial<AuthStrategyData> & {
loading: boolean; loading: boolean;
} => { } => {
const [data, setData] = useState<AuthStrategyData>(); const [data, setData] = useState<AuthStrategyData>();

View File

@@ -14,7 +14,7 @@ import { isFileAccepted } from "bknd/utils";
import { type FileWithPath, useDropzone } from "./use-dropzone"; import { type FileWithPath, useDropzone } from "./use-dropzone";
import { checkMaxReached } from "./helper"; import { checkMaxReached } from "./helper";
import { DropzoneInner } from "./DropzoneInner"; import { DropzoneInner } from "./DropzoneInner";
import { createDropzoneStore } from "ui/elements/media/dropzone-state"; import { createDropzoneStore } from "./dropzone-state";
import { useStore } from "zustand"; import { useStore } from "zustand";
export type FileState = { export type FileState = {

View File

@@ -1,9 +1,8 @@
import type { Api } from "bknd/client";
import type { PrimaryFieldType, RepoQueryIn } from "bknd"; import type { PrimaryFieldType, RepoQueryIn } from "bknd";
import type { MediaFieldSchema } from "media/AppMedia"; import type { MediaFieldSchema } from "media/AppMedia";
import type { TAppMediaConfig } from "media/media-schema"; import type { TAppMediaConfig } from "media/media-schema";
import { useId, useEffect, useRef, useState } from "react"; import { useId, useEffect, useRef, useState } from "react";
import { useApi, useApiInfiniteQuery, useApiQuery, useInvalidate } from "bknd/client"; import { type Api, useApi, useApiInfiniteQuery, useApiQuery, useInvalidate } from "bknd/client";
import { useEvent } from "ui/hooks/use-event"; import { useEvent } from "ui/hooks/use-event";
import { Dropzone, type DropzoneProps } from "./Dropzone"; import { Dropzone, type DropzoneProps } from "./Dropzone";
import { mediaItemsToFileStates } from "./helper"; import { mediaItemsToFileStates } from "./helper";
@@ -132,26 +131,24 @@ export function DropzoneContainer({
} }
return ( return (
<> <Dropzone
<Dropzone key={key}
key={key} getUploadInfo={getUploadInfo}
getUploadInfo={getUploadInfo} handleDelete={handleDelete}
handleDelete={handleDelete} autoUpload
autoUpload initialItems={_initialItems}
initialItems={_initialItems} footer={
footer={ infinite &&
infinite && "setSize" in $q && (
"setSize" in $q && ( <Footer
<Footer items={_initialItems.length}
items={_initialItems.length} length={placeholderLength}
length={placeholderLength} onFirstVisible={() => $q.setSize($q.size + 1)}
onFirstVisible={() => $q.setSize($q.size + 1)} />
/> )
) }
} {...props}
{...props} />
/>
</>
); );
} }

View File

@@ -19,8 +19,8 @@ import {
} from "react-icons/tb"; } from "react-icons/tb";
import { Dropdown, type DropdownItem } from "ui/components/overlay/Dropdown"; import { Dropdown, type DropdownItem } from "ui/components/overlay/Dropdown";
import { IconButton } from "ui/components/buttons/IconButton"; import { IconButton } from "ui/components/buttons/IconButton";
import { formatNumber } from "core/utils"; import { formatNumber } from "bknd/utils";
import type { DropzoneRenderProps, FileState } from "ui/elements"; import type { DropzoneRenderProps, FileState } from "./Dropzone";
import { useDropzoneFileState, useDropzoneState } from "./Dropzone"; import { useDropzoneFileState, useDropzoneState } from "./Dropzone";
function handleUploadError(e: unknown) { function handleUploadError(e: unknown) {

View File

@@ -10,7 +10,7 @@ import {
TbUser, TbUser,
TbX, TbX,
} from "react-icons/tb"; } from "react-icons/tb";
import { useAuth, useBkndWindowContext } from "ui/client"; import { useAuth, useBkndWindowContext } from "bknd/client";
import { useBknd } from "ui/client/bknd"; import { useBknd } from "ui/client/bknd";
import { useTheme } from "ui/client/use-theme"; import { useTheme } from "ui/client/use-theme";
import { Button } from "ui/components/buttons/Button"; import { Button } from "ui/components/buttons/Button";

View File

@@ -1,6 +1,6 @@
import type { ContextModalProps } from "@mantine/modals"; import type { ContextModalProps } from "@mantine/modals";
import { type ReactNode, useEffect, useMemo, useState } from "react"; import { type ReactNode, useEffect, useMemo, useState } from "react";
import { useEntityQuery } from "ui/client"; import { useEntityQuery } from "bknd/client";
import { type FileState, Media } from "ui/elements"; import { type FileState, Media } from "ui/elements";
import { autoFormatString, datetimeStringLocal, formatNumber } from "core/utils"; import { autoFormatString, datetimeStringLocal, formatNumber } from "core/utils";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";

View File

@@ -1,4 +1,4 @@
import { useApi, useInvalidate } from "ui/client"; import { useApi, useInvalidate } from "bknd/client";
import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth"; import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth";
import { routes, useNavigate } from "ui/lib/routes"; import { routes, useNavigate } from "ui/lib/routes";
import { bkndModals } from "ui/modals"; import { bkndModals } from "ui/modals";

View File

@@ -4,7 +4,7 @@ import type { EntityData } from "bknd";
import type { RelationField } from "data/relations"; import type { RelationField } from "data/relations";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { TbEye } from "react-icons/tb"; import { TbEye } from "react-icons/tb";
import { useEntityQuery } from "ui/client"; import { useEntityQuery } from "bknd/client";
import { useBknd } from "ui/client/bknd"; import { useBknd } from "ui/client/bknd";
import { Button } from "ui/components/buttons/Button"; import { Button } from "ui/components/buttons/Button";
import * as Formy from "ui/components/form/Formy"; import * as Formy from "ui/components/form/Formy";

View File

@@ -1,6 +1,6 @@
import clsx from "clsx"; import clsx from "clsx";
import { TbArrowRight, TbCircle, TbCircleCheckFilled, TbFingerprint } from "react-icons/tb"; import { TbArrowRight, TbCircle, TbCircleCheckFilled, TbFingerprint } from "react-icons/tb";
import { useApiQuery } from "ui/client"; import { useApiQuery } from "bknd/client";
import { useBknd } from "ui/client/bknd"; import { useBknd } from "ui/client/bknd";
import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth"; import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth";
import { ButtonLink, type ButtonLinkProps } from "ui/components/buttons/Button"; import { ButtonLink, type ButtonLinkProps } from "ui/components/buttons/Button";

View File

@@ -35,7 +35,7 @@ import { SegmentedControl, Tooltip } from "@mantine/core";
import { Popover } from "ui/components/overlay/Popover"; import { Popover } from "ui/components/overlay/Popover";
import { cn } from "ui/lib/utils"; import { cn } from "ui/lib/utils";
import { JsonViewer } from "ui/components/code/JsonViewer"; import { JsonViewer } from "ui/components/code/JsonViewer";
import { mountOnce, useApiQuery } from "ui/client"; import { mountOnce, useApiQuery } from "bknd/client";
import { CodePreview } from "ui/components/code/CodePreview"; import { CodePreview } from "ui/components/code/CodePreview";
import type { JsonError } from "json-schema-library"; import type { JsonError } from "json-schema-library";
import { Alert } from "ui/components/display/Alert"; import { Alert } from "ui/components/display/Alert";

View File

@@ -3,7 +3,7 @@ import { ucFirst } from "bknd/utils";
import type { Entity, EntityData, EntityRelation } from "bknd"; import type { Entity, EntityData, EntityRelation } from "bknd";
import { Fragment, useState } from "react"; import { Fragment, useState } from "react";
import { TbDots } from "react-icons/tb"; import { TbDots } from "react-icons/tb";
import { useApiQuery, useEntityQuery } from "ui/client"; import { useApiQuery, useEntityQuery } from "bknd/client";
import { useBkndData } from "ui/client/schema/data/use-bknd-data"; import { useBkndData } from "ui/client/schema/data/use-bknd-data";
import { Button } from "ui/components/buttons/Button"; import { Button } from "ui/components/buttons/Button";
import { IconButton } from "ui/components/buttons/IconButton"; import { IconButton } from "ui/components/buttons/IconButton";

View File

@@ -1,6 +1,6 @@
import type { EntityData } from "bknd"; import type { EntityData } from "bknd";
import { useState } from "react"; import { useState } from "react";
import { useEntityMutate } from "ui/client"; import { useEntityMutate } from "bknd/client";
import { useBkndData } from "ui/client/schema/data/use-bknd-data"; import { useBkndData } from "ui/client/schema/data/use-bknd-data";
import { Button } from "ui/components/buttons/Button"; import { Button } from "ui/components/buttons/Button";
import { Message } from "ui/components/display/Message"; import { Message } from "ui/components/display/Message";

View File

@@ -2,7 +2,7 @@ import type { Entity } from "bknd";
import { repoQuery } from "data/server/query"; import { repoQuery } from "data/server/query";
import { Fragment } from "react"; import { Fragment } from "react";
import { TbDots } from "react-icons/tb"; import { TbDots } from "react-icons/tb";
import { useApiQuery } from "ui/client"; import { useApiQuery } from "bknd/client";
import { useBknd } from "ui/client/bknd"; import { useBknd } from "ui/client/bknd";
import { useBkndData } from "ui/client/schema/data/use-bknd-data"; import { useBkndData } from "ui/client/schema/data/use-bknd-data";
import { Button } from "ui/components/buttons/Button"; import { Button } from "ui/components/buttons/Button";

View File

@@ -11,7 +11,7 @@ import SettingsRoutes from "./settings";
import { FlashMessage } from "ui/modules/server/FlashMessage"; import { FlashMessage } from "ui/modules/server/FlashMessage";
import { AuthRegister } from "ui/routes/auth/auth.register"; import { AuthRegister } from "ui/routes/auth/auth.register";
import { BkndModalsProvider } from "ui/modals"; import { BkndModalsProvider } from "ui/modals";
import { useBkndWindowContext } from "ui/client"; import { useBkndWindowContext } from "bknd/client";
import ToolsRoutes from "./tools"; import ToolsRoutes from "./tools";
// @ts-ignore // @ts-ignore

View File

@@ -1,11 +1,12 @@
import { IconPhoto } from "@tabler/icons-react"; import { IconPhoto } from "@tabler/icons-react";
import { useBknd } from "ui/client/BkndProvider"; import { useBknd } from "ui/client/BkndProvider";
import { Empty } from "ui/components/display/Empty"; import { Empty } from "ui/components/display/Empty";
import { type FileState, Media } from "ui/elements";
import { useBrowserTitle } from "ui/hooks/use-browser-title"; import { useBrowserTitle } from "ui/hooks/use-browser-title";
import * as AppShell from "ui/layouts/AppShell/AppShell"; import * as AppShell from "ui/layouts/AppShell/AppShell";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
import { bkndModals } from "ui/modals"; import { bkndModals } from "ui/modals";
import { DropzoneContainer } from "ui/elements/media/DropzoneContainer";
import type { FileState } from "ui/elements/media/Dropzone";
export function MediaIndex() { export function MediaIndex() {
const { config } = useBknd(); const { config } = useBknd();
@@ -35,7 +36,7 @@ export function MediaIndex() {
return ( return (
<AppShell.Scrollable> <AppShell.Scrollable>
<div className="flex flex-1 p-3"> <div className="flex flex-1 p-3">
<Media.Dropzone onClick={onClick} infinite query={{ sort: "-id" }} /> <DropzoneContainer onClick={onClick} query={{ sort: "-id" }} />
</div> </div>
</AppShell.Scrollable> </AppShell.Scrollable>
); );

View File

@@ -1,6 +1,6 @@
import { IconHome } from "@tabler/icons-react"; import { IconHome } from "@tabler/icons-react";
import { useEffect } from "react"; import { useEffect } from "react";
import { useAuth } from "ui/client"; import { useAuth } from "bknd/client";
import { useEffectOnce } from "ui/hooks/use-effect"; import { useEffectOnce } from "ui/hooks/use-effect";
import { Empty } from "../components/display/Empty"; import { Empty } from "../components/display/Empty";
import { useBrowserTitle } from "../hooks/use-browser-title"; import { useBrowserTitle } from "../hooks/use-browser-title";

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useApi, useApiQuery } from "ui/client"; import { useApi, useApiQuery } from "bknd/client";
import { Scrollable } from "ui/layouts/AppShell/AppShell"; import { Scrollable } from "ui/layouts/AppShell/AppShell";
function Bla() { function Bla() {

View File

@@ -35,7 +35,8 @@
"bknd/adapter": ["./src/adapter/index.ts"], "bknd/adapter": ["./src/adapter/index.ts"],
"bknd/adapter/*": ["./src/adapter/*/index.ts"], "bknd/adapter/*": ["./src/adapter/*/index.ts"],
"bknd/client": ["./src/ui/client/index.ts"], "bknd/client": ["./src/ui/client/index.ts"],
"bknd/modes": ["./src/modes/index.ts"] "bknd/modes": ["./src/modes/index.ts"],
"bknd/elements": ["./src/ui/elements/index.ts"]
} }
}, },
"include": [ "include": [