mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 20:37:21 +00:00
fix schema form
This commit is contained in:
53
app/src/ui/components/display/ErrorBoundary.tsx
Normal file
53
app/src/ui/components/display/ErrorBoundary.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React, { Component, type ErrorInfo, type ReactNode } from "react";
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
fallback?:
|
||||
| (({ error, resetError }: { error: Error; resetError: () => void }) => ReactNode)
|
||||
| ReactNode;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error?: Error | undefined;
|
||||
}
|
||||
|
||||
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: undefined };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
override componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
console.error("ErrorBoundary caught an error:", error, errorInfo);
|
||||
}
|
||||
|
||||
resetError = () => {
|
||||
this.setState({ hasError: false, error: undefined });
|
||||
};
|
||||
|
||||
override render() {
|
||||
if (this.state.hasError) {
|
||||
return this.props.fallback ? (
|
||||
typeof this.props.fallback === "function" ? (
|
||||
this.props.fallback({ error: this.state.error!, resetError: this.resetError })
|
||||
) : (
|
||||
this.props.fallback
|
||||
)
|
||||
) : (
|
||||
<div>
|
||||
<h2>Something went wrong.</h2>
|
||||
<button onClick={this.resetError}>Try Again</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
@@ -10,7 +10,6 @@ import { getLabel, getMultiSchemaMatched } from "./utils";
|
||||
|
||||
export type AnyOfFieldRootProps = {
|
||||
path?: string;
|
||||
schema?: JsonSchema;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
@@ -34,14 +33,14 @@ export const useAnyOfContext = () => {
|
||||
|
||||
const selectedAtom = atom<number | null>(null);
|
||||
|
||||
const Root = ({ path = "", schema: _schema, children }: AnyOfFieldRootProps) => {
|
||||
const Root = ({ path = "", children }: AnyOfFieldRootProps) => {
|
||||
const {
|
||||
setValue,
|
||||
lib,
|
||||
pointer,
|
||||
value: { matchedIndex, schemas },
|
||||
schema
|
||||
} = useDerivedFieldContext(path, _schema, (ctx) => {
|
||||
} = useDerivedFieldContext(path, (ctx) => {
|
||||
const [matchedIndex, schemas = []] = getMultiSchemaMatched(ctx.schema, ctx.value);
|
||||
return { matchedIndex, schemas };
|
||||
});
|
||||
@@ -115,7 +114,7 @@ const Select = () => {
|
||||
};
|
||||
|
||||
// @todo: add local validation for AnyOf fields
|
||||
const Field = ({ name, label, schema, ...props }: Partial<FormFieldProps>) => {
|
||||
const Field = ({ name, label, ...props }: Partial<FormFieldProps>) => {
|
||||
const { selected, selectedSchema, path, errors } = useAnyOfContext();
|
||||
if (selected === null) return null;
|
||||
return (
|
||||
|
||||
@@ -10,12 +10,8 @@ import { FieldWrapper } from "./FieldWrapper";
|
||||
import { useDerivedFieldContext, useFormValue } from "./Form";
|
||||
import { coerce, getMultiSchema, getMultiSchemaMatched, isEqual, suffixPath } from "./utils";
|
||||
|
||||
export const ArrayField = ({
|
||||
path = "",
|
||||
schema: _schema
|
||||
}: { path?: string; schema?: JsonSchema }) => {
|
||||
const { setValue, pointer, required, ...ctx } = useDerivedFieldContext(path, _schema);
|
||||
const schema = _schema ?? ctx.schema;
|
||||
export const ArrayField = ({ path = "" }: { path?: string }) => {
|
||||
const { setValue, pointer, required, schema, ...ctx } = useDerivedFieldContext(path);
|
||||
if (!schema || typeof schema === "undefined") return `ArrayField(${path}): no schema ${pointer}`;
|
||||
|
||||
// if unique items with enum
|
||||
@@ -55,7 +51,7 @@ export const ArrayField = ({
|
||||
};
|
||||
|
||||
const ArrayItem = memo(({ path, index, schema }: any) => {
|
||||
const { value, ...ctx } = useDerivedFieldContext(path, schema, (ctx) => {
|
||||
const { value, ...ctx } = useDerivedFieldContext(path, (ctx) => {
|
||||
return ctx.value?.[index];
|
||||
});
|
||||
const itemPath = suffixPath(path, index);
|
||||
@@ -107,7 +103,7 @@ const ArrayAdd = ({ schema, path }: { schema: JsonSchema; path: string }) => {
|
||||
setValue,
|
||||
value: { currentIndex },
|
||||
...ctx
|
||||
} = useDerivedFieldContext(path, schema, (ctx) => {
|
||||
} = useDerivedFieldContext(path, (ctx) => {
|
||||
return { currentIndex: ctx.value?.length ?? 0 };
|
||||
});
|
||||
const itemsMultiSchema = getMultiSchema(schema.items);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { JsonSchema } from "json-schema-library";
|
||||
import type { ChangeEvent, ComponentPropsWithoutRef } from "react";
|
||||
import ErrorBoundary from "ui/components/display/ErrorBoundary";
|
||||
import * as Formy from "ui/components/form/Formy";
|
||||
import { useEvent } from "ui/hooks/use-event";
|
||||
import { ArrayField } from "./ArrayField";
|
||||
@@ -10,11 +11,28 @@ import { coerce, isType, isTypeSchema } from "./utils";
|
||||
|
||||
export type FieldProps = {
|
||||
onChange?: (e: ChangeEvent<any>) => void;
|
||||
} & Omit<FieldwrapperProps, "children">;
|
||||
placeholder?: string;
|
||||
} & Omit<FieldwrapperProps, "children" | "schema">;
|
||||
|
||||
export const Field = ({ name, schema: _schema, onChange, ...props }: FieldProps) => {
|
||||
const { path, setValue, required, ...ctx } = useDerivedFieldContext(name, _schema);
|
||||
const schema = _schema ?? ctx.schema;
|
||||
export const Field = (props: FieldProps) => {
|
||||
return (
|
||||
<ErrorBoundary fallback={fieldErrorBoundary(props)}>
|
||||
<FieldImpl {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
const fieldErrorBoundary =
|
||||
({ name }: FieldProps) =>
|
||||
({ error }: { error: Error }) => (
|
||||
<Pre>
|
||||
Field "{name}" error: {error.message}
|
||||
</Pre>
|
||||
);
|
||||
|
||||
const FieldImpl = ({ name, onChange, placeholder, ...props }: FieldProps) => {
|
||||
const { path, setValue, required, schema, ...ctx } = useDerivedFieldContext(name);
|
||||
//console.log("Field", { name, path, schema });
|
||||
if (!isTypeSchema(schema))
|
||||
return (
|
||||
<Pre>
|
||||
@@ -23,11 +41,11 @@ export const Field = ({ name, schema: _schema, onChange, ...props }: FieldProps)
|
||||
);
|
||||
|
||||
if (isType(schema.type, "object")) {
|
||||
return <ObjectField path={name} schema={schema} />;
|
||||
return <ObjectField path={name} />;
|
||||
}
|
||||
|
||||
if (isType(schema.type, "array")) {
|
||||
return <ArrayField path={name} schema={schema} />;
|
||||
return <ArrayField path={name} />;
|
||||
}
|
||||
|
||||
const disabled = schema.readOnly ?? "const" in schema ?? false;
|
||||
@@ -48,6 +66,7 @@ export const Field = ({ name, schema: _schema, onChange, ...props }: FieldProps)
|
||||
name={name}
|
||||
required={required}
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
onChange={onChange ?? handleChange}
|
||||
/>
|
||||
</FieldWrapper>
|
||||
@@ -69,7 +88,9 @@ export const FieldComponent = ({
|
||||
const props = {
|
||||
..._props,
|
||||
// allow override
|
||||
value: typeof _props.value !== "undefined" ? _props.value : value
|
||||
value: typeof _props.value !== "undefined" ? _props.value : value,
|
||||
placeholder:
|
||||
(_props.placeholder ?? typeof schema.default !== "undefined") ? String(schema.default) : ""
|
||||
};
|
||||
|
||||
if (schema.enum) {
|
||||
|
||||
@@ -298,7 +298,6 @@ type SelectorFn<Ctx = any, Refined = any> = (state: Ctx) => Refined;
|
||||
|
||||
export function useDerivedFieldContext<Data = any, Reduced = undefined>(
|
||||
path,
|
||||
_schema?: LibJsonSchema,
|
||||
deriveFn?: SelectorFn<
|
||||
FormContext<Data> & {
|
||||
pointer: string;
|
||||
@@ -314,8 +313,7 @@ export function useDerivedFieldContext<Data = any, Reduced = undefined>(
|
||||
required: boolean;
|
||||
path: string;
|
||||
} {
|
||||
const { _formStateAtom, root, lib, ...ctx } = useFormContext();
|
||||
const schema = _schema ?? ctx.schema;
|
||||
const { _formStateAtom, root, lib, schema, ...ctx } = useFormContext();
|
||||
const selected = selectAtom(
|
||||
_formStateAtom,
|
||||
useCallback(
|
||||
|
||||
@@ -7,21 +7,14 @@ import { useDerivedFieldContext } from "./Form";
|
||||
|
||||
export type ObjectFieldProps = {
|
||||
path?: string;
|
||||
schema?: Exclude<JSONSchema, boolean>;
|
||||
label?: string | false;
|
||||
wrapperProps?: Partial<FieldwrapperProps>;
|
||||
};
|
||||
|
||||
export const ObjectField = ({
|
||||
path = "",
|
||||
schema: _schema,
|
||||
label: _label,
|
||||
wrapperProps = {}
|
||||
}: ObjectFieldProps) => {
|
||||
const ctx = useDerivedFieldContext(path, _schema);
|
||||
const schema = _schema ?? ctx.schema;
|
||||
export const ObjectField = ({ path = "", label: _label, wrapperProps = {} }: ObjectFieldProps) => {
|
||||
const { schema } = useDerivedFieldContext(path);
|
||||
if (!isTypeSchema(schema)) return `ObjectField "${path}": no schema`;
|
||||
const properties = schema.properties ?? {};
|
||||
const properties = Object.entries(schema.properties ?? {}) as [string, JSONSchema][];
|
||||
|
||||
return (
|
||||
<FieldWrapper
|
||||
@@ -31,17 +24,20 @@ export const ObjectField = ({
|
||||
errorPlacement="top"
|
||||
{...wrapperProps}
|
||||
>
|
||||
{Object.keys(properties).map((prop) => {
|
||||
const schema = properties[prop];
|
||||
const name = [path, prop].filter(Boolean).join(".");
|
||||
if (typeof schema === "undefined" || typeof schema === "boolean") return;
|
||||
{properties.length === 0 ? (
|
||||
<i className="opacity-50">No properties</i>
|
||||
) : (
|
||||
properties.map(([prop, schema]) => {
|
||||
const name = [path, prop].filter(Boolean).join(".");
|
||||
if (typeof schema === "undefined" || typeof schema === "boolean") return;
|
||||
|
||||
if (schema.anyOf || schema.oneOf) {
|
||||
return <AnyOfField key={name} path={name} />;
|
||||
}
|
||||
if (schema.anyOf || schema.oneOf) {
|
||||
return <AnyOfField key={name} path={name} />;
|
||||
}
|
||||
|
||||
return <Field key={name} name={name} />;
|
||||
})}
|
||||
return <Field key={name} name={name} />;
|
||||
})
|
||||
)}
|
||||
</FieldWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user