mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 21:06:04 +00:00
form rerenders optimized
This commit is contained in:
@@ -1,28 +1,29 @@
|
||||
import type { JsonError } from "json-schema-library";
|
||||
import type { JSONSchema } from "json-schema-to-ts";
|
||||
import { type ChangeEvent, type ReactNode, createContext, useContext, useState } from "react";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import type { JsonError, JsonSchema } from "json-schema-library";
|
||||
import { type ChangeEvent, type ReactNode, createContext, useContext, useMemo } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import * as Formy from "ui/components/form/Formy";
|
||||
import { useEvent } from "ui/hooks/use-event";
|
||||
import { FieldComponent, Field as FormField, type FieldProps as FormFieldProps } from "./Field";
|
||||
import { FormContextOverride, useFieldContext } from "./Form";
|
||||
import { FormContextOverride, useDerivedFieldContext } from "./Form";
|
||||
import { getLabel, getMultiSchemaMatched } from "./utils";
|
||||
|
||||
export type AnyOfFieldRootProps = {
|
||||
path?: string;
|
||||
schema?: Exclude<JSONSchema, boolean>;
|
||||
schema?: JsonSchema;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export type AnyOfFieldContext = {
|
||||
path: string;
|
||||
schema: Exclude<JSONSchema, boolean>;
|
||||
schemas?: JSONSchema[];
|
||||
selectedSchema?: Exclude<JSONSchema, boolean>;
|
||||
schema: JsonSchema;
|
||||
schemas?: JsonSchema[];
|
||||
selectedSchema?: JsonSchema;
|
||||
selected: number | null;
|
||||
select: (index: number | null) => void;
|
||||
options: string[];
|
||||
errors: JsonError[];
|
||||
selectSchema: JSONSchema;
|
||||
selectSchema: JsonSchema;
|
||||
};
|
||||
|
||||
const AnyOfContext = createContext<AnyOfFieldContext>(undefined!);
|
||||
@@ -31,61 +32,80 @@ export const useAnyOfContext = () => {
|
||||
return useContext(AnyOfContext);
|
||||
};
|
||||
|
||||
const selectedAtom = atom<number | null>(null);
|
||||
|
||||
const Root = ({ path = "", schema: _schema, children }: AnyOfFieldRootProps) => {
|
||||
const { setValue, pointer, lib, value, errors, ...ctx } = useFieldContext(path);
|
||||
const schema = _schema ?? ctx.schema;
|
||||
const {
|
||||
setValue,
|
||||
lib,
|
||||
pointer,
|
||||
errors,
|
||||
value: { matchedIndex, schemas },
|
||||
schema
|
||||
} = useDerivedFieldContext(path, _schema, (ctx) => {
|
||||
const [matchedIndex, schemas = []] = getMultiSchemaMatched(ctx.schema, ctx.value);
|
||||
return { matchedIndex, schemas };
|
||||
});
|
||||
if (!schema) return `AnyOfField(${path}): no schema ${pointer}`;
|
||||
const [matchedIndex, schemas = []] = getMultiSchemaMatched(schema, value);
|
||||
const [selected, setSelected] = useState<number | null>(matchedIndex > -1 ? matchedIndex : null);
|
||||
const options = schemas.map((s, i) => s.title ?? `Option ${i + 1}`);
|
||||
const selectSchema = {
|
||||
type: "string",
|
||||
enum: options
|
||||
} satisfies JSONSchema;
|
||||
//console.log("AnyOf:root", { value, matchedIndex, selected, schema });
|
||||
const [_selected, setSelected] = useAtom(selectedAtom);
|
||||
const selected = _selected !== null ? _selected : matchedIndex > -1 ? matchedIndex : null;
|
||||
|
||||
const selectedSchema =
|
||||
selected !== null ? (schemas[selected] as Exclude<JSONSchema, boolean>) : undefined;
|
||||
|
||||
function select(index: number | null) {
|
||||
const select = useEvent((index: number | null) => {
|
||||
setValue(pointer, index !== null ? lib.getTemplate(undefined, schemas[index]) : undefined);
|
||||
setSelected(index);
|
||||
}
|
||||
});
|
||||
|
||||
const context = useMemo(() => {
|
||||
const options = schemas.map((s, i) => s.title ?? `Option ${i + 1}`);
|
||||
const selectSchema = {
|
||||
type: "string",
|
||||
enum: options
|
||||
} satisfies JsonSchema;
|
||||
|
||||
const selectedSchema = selected !== null ? (schemas[selected] as JsonSchema) : undefined;
|
||||
|
||||
return {
|
||||
options,
|
||||
selectSchema,
|
||||
selectedSchema,
|
||||
schema,
|
||||
schemas,
|
||||
selected
|
||||
};
|
||||
}, [selected]);
|
||||
|
||||
return (
|
||||
<AnyOfContext.Provider
|
||||
key={selected}
|
||||
value={{
|
||||
selected,
|
||||
...context,
|
||||
select,
|
||||
options,
|
||||
selectSchema,
|
||||
path,
|
||||
schema,
|
||||
schemas,
|
||||
selectedSchema,
|
||||
errors
|
||||
}}
|
||||
>
|
||||
{/*<pre>{JSON.stringify({ value, selected, errors: errors.length }, null, 2)}</pre>*/}
|
||||
{children}
|
||||
</AnyOfContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const Select = () => {
|
||||
const { selected, select, path, schema, selectSchema } = useAnyOfContext();
|
||||
const { selected, select, path, schema, options, selectSchema } = useAnyOfContext();
|
||||
|
||||
function handleSelect(e: ChangeEvent<HTMLInputElement>) {
|
||||
//console.log("selected", e.target.value);
|
||||
const i = e.target.value ? Number(e.target.value) : null;
|
||||
select(i);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Formy.Label>{getLabel(path, schema)}</Formy.Label>
|
||||
<Formy.Label>
|
||||
{getLabel(path, schema)} {selected}
|
||||
</Formy.Label>
|
||||
<FieldComponent
|
||||
schema={selectSchema as any}
|
||||
/* @ts-ignore */
|
||||
options={options.map((label, value) => ({ label, value }))}
|
||||
onChange={handleSelect}
|
||||
value={selected ?? undefined}
|
||||
className="h-8 py-1"
|
||||
@@ -94,11 +114,11 @@ const Select = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const Field = ({ name, label, ...props }: Partial<FormFieldProps>) => {
|
||||
const Field = ({ name, label, schema, ...props }: Partial<FormFieldProps>) => {
|
||||
const { selected, selectedSchema, path, errors } = useAnyOfContext();
|
||||
if (selected === null) return null;
|
||||
return (
|
||||
<FormContextOverride path={path} schema={selectedSchema} overrideData>
|
||||
<FormContextOverride prefix={path} schema={selectedSchema}>
|
||||
<div className={twMerge(errors.length > 0 && "bg-red-500/10")}>
|
||||
<FormField key={`${path}_${selected}`} name={""} label={false} {...props} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user