form rerenders optimized

This commit is contained in:
dswbx
2025-02-07 16:11:21 +01:00
parent 02e7e1ca95
commit 324d641410
15 changed files with 546 additions and 339 deletions

View File

@@ -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>