mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-03 04:18:15 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7103ce519 | ||
|
|
43ad534482 | ||
|
|
ea97aff3e0 |
@@ -781,9 +781,10 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
|||||||
const setValue = isBound ? setBoundValue : setLocalValue;
|
const setValue = isBound ? setBoundValue : setLocalValue;
|
||||||
|
|
||||||
const hasValidation = !!(bindings?.value && props.checks?.length);
|
const hasValidation = !!(bindings?.value && props.checks?.length);
|
||||||
const { errors, validate } = useFieldValidation(bindings?.value ?? "", {
|
const { errors, validate } = useFieldValidation(
|
||||||
checks: props.checks ?? [],
|
bindings?.value ?? "",
|
||||||
});
|
hasValidation ? { checks: props.checks ?? [] } : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -822,9 +823,10 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
|||||||
const setValue = isBound ? setBoundValue : setLocalValue;
|
const setValue = isBound ? setBoundValue : setLocalValue;
|
||||||
|
|
||||||
const hasValidation = !!(bindings?.value && props.checks?.length);
|
const hasValidation = !!(bindings?.value && props.checks?.length);
|
||||||
const { errors, validate } = useFieldValidation(bindings?.value ?? "", {
|
const { errors, validate } = useFieldValidation(
|
||||||
checks: props.checks ?? [],
|
bindings?.value ?? "",
|
||||||
});
|
hasValidation ? { checks: props.checks ?? [] } : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -864,9 +866,10 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const hasValidation = !!(bindings?.value && props.checks?.length);
|
const hasValidation = !!(bindings?.value && props.checks?.length);
|
||||||
const { errors, validate } = useFieldValidation(bindings?.value ?? "", {
|
const { errors, validate } = useFieldValidation(
|
||||||
checks: props.checks ?? [],
|
bindings?.value ?? "",
|
||||||
});
|
hasValidation ? { checks: props.checks ?? [] } : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# @json-render/codegen
|
# @json-render/codegen
|
||||||
|
|
||||||
|
## 0.6.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @json-render/core@0.6.1
|
||||||
|
|
||||||
|
## 0.6.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [ea97aff]
|
||||||
|
- @json-render/core@0.6.1
|
||||||
|
|
||||||
## 0.6.0
|
## 0.6.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/codegen",
|
"name": "@json-render/codegen",
|
||||||
"version": "0.6.0",
|
"version": "0.6.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Utilities for generating code from json-render UI trees",
|
"description": "Utilities for generating code from json-render UI trees",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# @json-render/core
|
# @json-render/core
|
||||||
|
|
||||||
|
## 0.6.1
|
||||||
|
|
||||||
|
## 0.6.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- ea97aff: Fix infinite re-render loop caused by multiple unbound form inputs (Input, Textarea, Select) all registering field validation at the same empty path with different `checks` configs, causing them to overwrite each other endlessly. Stabilize context values in ActionProvider, ValidationProvider, and useUIStream by using refs for state/callbacks, preventing unnecessary re-render cascades on every state update.
|
||||||
|
|
||||||
## 0.6.0
|
## 0.6.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/core",
|
"name": "@json-render/core",
|
||||||
"version": "0.6.0",
|
"version": "0.6.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "JSON becomes real things. Define your catalog, register your components, let AI generate.",
|
"description": "JSON becomes real things. Define your catalog, register your components, let AI generate.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# @json-render/react-native
|
# @json-render/react-native
|
||||||
|
|
||||||
|
## 0.6.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @json-render/core@0.6.1
|
||||||
|
|
||||||
|
## 0.6.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [ea97aff]
|
||||||
|
- @json-render/core@0.6.1
|
||||||
|
|
||||||
## 0.6.0
|
## 0.6.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/react-native",
|
"name": "@json-render/react-native",
|
||||||
"version": "0.6.0",
|
"version": "0.6.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "React Native renderer for @json-render/core. JSON becomes React Native components.",
|
"description": "React Native renderer for @json-render/core. JSON becomes React Native components.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
# @json-render/react
|
# @json-render/react
|
||||||
|
|
||||||
|
## 0.6.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 43ad534: Fix infinite re-render loop caused by multiple unbound form inputs (Input, Textarea, Select) all registering field validation at the same empty path with different `checks` configs, causing them to overwrite each other endlessly. Stabilize context values in ActionProvider, ValidationProvider, and useUIStream by using refs for state/callbacks, preventing unnecessary re-render cascades on every state update.
|
||||||
|
- @json-render/core@0.6.1
|
||||||
|
|
||||||
|
## 0.6.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- ea97aff: Fix infinite re-render loop caused by multiple unbound form inputs (Input, Textarea, Select) all registering field validation at the same empty path with different `checks` configs, causing them to overwrite each other endlessly. Stabilize context values in ActionProvider, ValidationProvider, and useUIStream by using refs for state/callbacks, preventing unnecessary re-render cascades on every state update.
|
||||||
|
- Updated dependencies [ea97aff]
|
||||||
|
- @json-render/core@0.6.1
|
||||||
|
|
||||||
## 0.6.0
|
## 0.6.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/react",
|
"name": "@json-render/react",
|
||||||
"version": "0.6.0",
|
"version": "0.6.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "React renderer for @json-render/core. JSON becomes React components.",
|
"description": "React renderer for @json-render/core. JSON becomes React components.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
useCallback,
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
useRef,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
} from "react";
|
} from "react";
|
||||||
import {
|
import {
|
||||||
@@ -137,6 +138,11 @@ export function ActionProvider({
|
|||||||
children,
|
children,
|
||||||
}: ActionProviderProps) {
|
}: ActionProviderProps) {
|
||||||
const { state, get, set } = useStateStore();
|
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] =
|
const [handlers, setHandlers] =
|
||||||
useState<Record<string, ActionHandler>>(initialHandlers);
|
useState<Record<string, ActionHandler>>(initialHandlers);
|
||||||
const [loadingActions, setLoadingActions] = useState<Set<string>>(new Set());
|
const [loadingActions, setLoadingActions] = useState<Set<string>>(new Set());
|
||||||
@@ -152,7 +158,7 @@ export function ActionProvider({
|
|||||||
|
|
||||||
const execute = useCallback(
|
const execute = useCallback(
|
||||||
async (binding: ActionBinding) => {
|
async (binding: ActionBinding) => {
|
||||||
const resolved = resolveAction(binding, state);
|
const resolved = resolveAction(binding, stateRef.current);
|
||||||
|
|
||||||
// Built-in: setState updates the StateProvider state directly
|
// Built-in: setState updates the StateProvider state directly
|
||||||
if (resolved.action === "setState" && resolved.params) {
|
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(() => {
|
const confirm = useCallback(() => {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
useCallback,
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
useRef,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
} from "react";
|
} from "react";
|
||||||
import {
|
import {
|
||||||
@@ -130,6 +131,11 @@ export function ValidationProvider({
|
|||||||
children,
|
children,
|
||||||
}: ValidationProviderProps) {
|
}: ValidationProviderProps) {
|
||||||
const { state } = useStateStore();
|
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<
|
const [fieldStates, setFieldStates] = useState<
|
||||||
Record<string, FieldValidationState>
|
Record<string, FieldValidationState>
|
||||||
>({});
|
>({});
|
||||||
@@ -155,8 +161,9 @@ export function ValidationProvider({
|
|||||||
const validate = useCallback(
|
const validate = useCallback(
|
||||||
(path: string, config: ValidationConfig): ValidationResult => {
|
(path: string, config: ValidationConfig): ValidationResult => {
|
||||||
// Walk the nested state object using JSON Pointer segments
|
// Walk the nested state object using JSON Pointer segments
|
||||||
|
const currentState = stateRef.current;
|
||||||
const segments = path.split("/").filter(Boolean);
|
const segments = path.split("/").filter(Boolean);
|
||||||
let value: unknown = state;
|
let value: unknown = currentState;
|
||||||
for (const seg of segments) {
|
for (const seg of segments) {
|
||||||
if (value != null && typeof value === "object") {
|
if (value != null && typeof value === "object") {
|
||||||
value = (value as Record<string, unknown>)[seg];
|
value = (value as Record<string, unknown>)[seg];
|
||||||
@@ -167,7 +174,7 @@ export function ValidationProvider({
|
|||||||
}
|
}
|
||||||
const result = runValidation(config, {
|
const result = runValidation(config, {
|
||||||
value,
|
value,
|
||||||
stateModel: state,
|
stateModel: currentState,
|
||||||
customFunctions,
|
customFunctions,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -182,7 +189,7 @@ export function ValidationProvider({
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
[state, customFunctions],
|
[customFunctions],
|
||||||
);
|
);
|
||||||
|
|
||||||
const touch = useCallback((path: string) => {
|
const touch = useCallback((path: string) => {
|
||||||
@@ -280,7 +287,7 @@ export function useFieldValidation(
|
|||||||
|
|
||||||
// Register field on mount
|
// Register field on mount
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (config) {
|
if (path && config) {
|
||||||
registerField(path, config);
|
registerField(path, config);
|
||||||
}
|
}
|
||||||
}, [path, config, registerField]);
|
}, [path, config, registerField]);
|
||||||
|
|||||||
@@ -250,6 +250,13 @@ export function useUIStream({
|
|||||||
const [rawLines, setRawLines] = useState<string[]>([]);
|
const [rawLines, setRawLines] = useState<string[]>([]);
|
||||||
const abortControllerRef = useRef<AbortController | null>(null);
|
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(() => {
|
const clear = useCallback(() => {
|
||||||
setSpec(null);
|
setSpec(null);
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -350,19 +357,19 @@ export function useUIStream({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onComplete?.(currentSpec);
|
onCompleteRef.current?.(currentSpec);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if ((err as Error).name === "AbortError") {
|
if ((err as Error).name === "AbortError") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const error = err instanceof Error ? err : new Error(String(err));
|
const error = err instanceof Error ? err : new Error(String(err));
|
||||||
setError(error);
|
setError(error);
|
||||||
onError?.(error);
|
onErrorRef.current?.(error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsStreaming(false);
|
setIsStreaming(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[api, onComplete, onError],
|
[api],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Cleanup on unmount
|
// Cleanup on unmount
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# @json-render/remotion
|
# @json-render/remotion
|
||||||
|
|
||||||
|
## 0.6.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @json-render/core@0.6.1
|
||||||
|
|
||||||
|
## 0.6.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [ea97aff]
|
||||||
|
- @json-render/core@0.6.1
|
||||||
|
|
||||||
## 0.6.0
|
## 0.6.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/remotion",
|
"name": "@json-render/remotion",
|
||||||
"version": "0.6.0",
|
"version": "0.6.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Remotion renderer for @json-render/core. JSON becomes video compositions.",
|
"description": "Remotion renderer for @json-render/core. JSON becomes video compositions.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
Reference in New Issue
Block a user