mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-03 04:18:15 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df91473560 |
@@ -781,9 +781,10 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
||||
const setValue = isBound ? setBoundValue : setLocalValue;
|
||||
|
||||
const hasValidation = !!(bindings?.value && props.checks?.length);
|
||||
const { errors, validate } = useFieldValidation(bindings?.value ?? "", {
|
||||
checks: props.checks ?? [],
|
||||
});
|
||||
const { errors, validate } = useFieldValidation(
|
||||
bindings?.value ?? "",
|
||||
hasValidation ? { checks: props.checks ?? [] } : undefined,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
@@ -822,9 +823,10 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
||||
const setValue = isBound ? setBoundValue : setLocalValue;
|
||||
|
||||
const hasValidation = !!(bindings?.value && props.checks?.length);
|
||||
const { errors, validate } = useFieldValidation(bindings?.value ?? "", {
|
||||
checks: props.checks ?? [],
|
||||
});
|
||||
const { errors, validate } = useFieldValidation(
|
||||
bindings?.value ?? "",
|
||||
hasValidation ? { checks: props.checks ?? [] } : undefined,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
@@ -864,9 +866,10 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
||||
);
|
||||
|
||||
const hasValidation = !!(bindings?.value && props.checks?.length);
|
||||
const { errors, validate } = useFieldValidation(bindings?.value ?? "", {
|
||||
checks: props.checks ?? [],
|
||||
});
|
||||
const { errors, validate } = useFieldValidation(
|
||||
bindings?.value ?? "",
|
||||
hasValidation ? { checks: props.checks ?? [] } : undefined,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -6,6 +6,7 @@ import React, {
|
||||
useState,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import {
|
||||
@@ -137,6 +138,11 @@ export function ActionProvider({
|
||||
children,
|
||||
}: ActionProviderProps) {
|
||||
const { state, get, set } = useStateStore();
|
||||
// Keep a ref to the latest state so `execute` doesn't change on every
|
||||
// state update — preventing the entire action context from churning.
|
||||
const stateRef = useRef(state);
|
||||
stateRef.current = state;
|
||||
|
||||
const [handlers, setHandlers] =
|
||||
useState<Record<string, ActionHandler>>(initialHandlers);
|
||||
const [loadingActions, setLoadingActions] = useState<Set<string>>(new Set());
|
||||
@@ -152,7 +158,7 @@ export function ActionProvider({
|
||||
|
||||
const execute = useCallback(
|
||||
async (binding: ActionBinding) => {
|
||||
const resolved = resolveAction(binding, state);
|
||||
const resolved = resolveAction(binding, stateRef.current);
|
||||
|
||||
// Built-in: setState updates the StateProvider state directly
|
||||
if (resolved.action === "setState" && resolved.params) {
|
||||
@@ -299,7 +305,7 @@ export function ActionProvider({
|
||||
});
|
||||
}
|
||||
},
|
||||
[state, handlers, get, set, navigate],
|
||||
[handlers, get, set, navigate],
|
||||
);
|
||||
|
||||
const confirm = useCallback(() => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import React, {
|
||||
useState,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import {
|
||||
@@ -130,6 +131,11 @@ export function ValidationProvider({
|
||||
children,
|
||||
}: ValidationProviderProps) {
|
||||
const { state } = useStateStore();
|
||||
// Keep a ref to the latest state so `validate` doesn't change on every
|
||||
// state update — preventing the entire validation context from churning.
|
||||
const stateRef = useRef(state);
|
||||
stateRef.current = state;
|
||||
|
||||
const [fieldStates, setFieldStates] = useState<
|
||||
Record<string, FieldValidationState>
|
||||
>({});
|
||||
@@ -155,8 +161,9 @@ export function ValidationProvider({
|
||||
const validate = useCallback(
|
||||
(path: string, config: ValidationConfig): ValidationResult => {
|
||||
// Walk the nested state object using JSON Pointer segments
|
||||
const currentState = stateRef.current;
|
||||
const segments = path.split("/").filter(Boolean);
|
||||
let value: unknown = state;
|
||||
let value: unknown = currentState;
|
||||
for (const seg of segments) {
|
||||
if (value != null && typeof value === "object") {
|
||||
value = (value as Record<string, unknown>)[seg];
|
||||
@@ -167,7 +174,7 @@ export function ValidationProvider({
|
||||
}
|
||||
const result = runValidation(config, {
|
||||
value,
|
||||
stateModel: state,
|
||||
stateModel: currentState,
|
||||
customFunctions,
|
||||
});
|
||||
|
||||
@@ -182,7 +189,7 @@ export function ValidationProvider({
|
||||
|
||||
return result;
|
||||
},
|
||||
[state, customFunctions],
|
||||
[customFunctions],
|
||||
);
|
||||
|
||||
const touch = useCallback((path: string) => {
|
||||
@@ -280,7 +287,7 @@ export function useFieldValidation(
|
||||
|
||||
// Register field on mount
|
||||
React.useEffect(() => {
|
||||
if (config) {
|
||||
if (path && config) {
|
||||
registerField(path, config);
|
||||
}
|
||||
}, [path, config, registerField]);
|
||||
|
||||
@@ -250,6 +250,13 @@ export function useUIStream({
|
||||
const [rawLines, setRawLines] = useState<string[]>([]);
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
|
||||
// Keep refs to callbacks so `send` doesn't recreate when consumers
|
||||
// pass inline arrow functions.
|
||||
const onCompleteRef = useRef(onComplete);
|
||||
onCompleteRef.current = onComplete;
|
||||
const onErrorRef = useRef(onError);
|
||||
onErrorRef.current = onError;
|
||||
|
||||
const clear = useCallback(() => {
|
||||
setSpec(null);
|
||||
setError(null);
|
||||
@@ -350,19 +357,19 @@ export function useUIStream({
|
||||
}
|
||||
}
|
||||
|
||||
onComplete?.(currentSpec);
|
||||
onCompleteRef.current?.(currentSpec);
|
||||
} catch (err) {
|
||||
if ((err as Error).name === "AbortError") {
|
||||
return;
|
||||
}
|
||||
const error = err instanceof Error ? err : new Error(String(err));
|
||||
setError(error);
|
||||
onError?.(error);
|
||||
onErrorRef.current?.(error);
|
||||
} finally {
|
||||
setIsStreaming(false);
|
||||
}
|
||||
},
|
||||
[api, onComplete, onError],
|
||||
[api],
|
||||
);
|
||||
|
||||
// Cleanup on unmount
|
||||
|
||||
Reference in New Issue
Block a user