deep merge schema for advanced fields

This commit is contained in:
Josh Hawkins 2026-01-28 09:28:06 -06:00
parent de6ad5a32a
commit 8c65cbce22
2 changed files with 60 additions and 12 deletions

View File

@ -8,7 +8,7 @@ import { transformSchema } from "@/lib/config-schema";
import { createErrorTransformer } from "@/lib/config-schema/errorMessages";
import { useMemo, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils";
import { cn, mergeUiSchema } from "@/lib/utils";
export interface ConfigFormProps {
/** JSON Schema for the form */
@ -90,17 +90,22 @@ export function ConfigForm({
);
// Merge generated uiSchema with custom overrides
const finalUiSchema = useMemo(
() => ({
...generatedUiSchema,
"ui:groups": fieldGroups,
...customUiSchema,
"ui:submitButtonOptions": showSubmit
? { norender: false }
: { norender: true },
}),
[generatedUiSchema, customUiSchema, showSubmit, fieldGroups],
);
const finalUiSchema = useMemo(() => {
// Start with generated schema
const merged = mergeUiSchema(generatedUiSchema, customUiSchema);
// Add field groups
if (fieldGroups) {
merged["ui:groups"] = fieldGroups;
}
// Set submit button options
merged["ui:submitButtonOptions"] = showSubmit
? { norender: false }
: { norender: true };
return merged;
}, [generatedUiSchema, customUiSchema, showSubmit, fieldGroups]);
// Create error transformer for user-friendly error messages
const errorTransformer = useMemo(() => createErrorTransformer(i18n), [i18n]);

View File

@ -1,6 +1,49 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import type { UiSchema } from "@rjsf/utils";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
/**
* Deep merges uiSchema objects, preserving nested properties from the base schema
* when overrides don't explicitly replace them.
*
* Special handling for ui:options - merges nested options rather than replacing them.
*/
export function mergeUiSchema(
base: UiSchema = {},
overrides: UiSchema = {},
): UiSchema {
const result: UiSchema = { ...base };
for (const [key, value] of Object.entries(overrides)) {
if (
key === "ui:options" &&
base[key] &&
typeof value === "object" &&
value !== null
) {
// Merge ui:options objects instead of replacing
result[key] = {
...(typeof base[key] === "object" && base[key] !== null
? base[key]
: {}),
...value,
};
} else if (
typeof value === "object" &&
value !== null &&
!Array.isArray(value)
) {
// Recursively merge nested objects (field configurations)
result[key] = mergeUiSchema(base[key] as UiSchema, value as UiSchema);
} else {
// Replace primitive values and arrays
result[key] = value;
}
}
return result;
}