mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-21 06:30:33 +08:00
* external store adapter for state management Introduces a `StateStore` interface that lets users plug in their own state management (Redux, Zustand, XState, etc.) instead of being locked into the internal `useState`-based store. - Added `StateStore` interface and `createStateStore()` factory to `@json-render/core` - `StateProvider`, `JSONUIProvider`, and `createRenderer` now accept an optional `store` prop for controlled mode - When `store` is provided, it becomes the single source of truth (`initialState`/`onStateChange` are ignored) - When `store` is omitted, everything works exactly as before (fully backward compatible) - Applied across all platform packages: react, react-native, react-pdf * improvements * update docs * improvements * fixes * fix CI * add store adapters * fixes * fixes * fixes * fixes * e2e tests * improvements * fixes * fixes * fixes * fixes * update lockfile for widened react peer deps * fix dashboard build
77 lines
1.9 KiB
TypeScript
77 lines
1.9 KiB
TypeScript
"use client";
|
|
|
|
import { useMemo, useRef, type ReactNode } from "react";
|
|
import {
|
|
Renderer,
|
|
type ComponentRenderer,
|
|
type Spec,
|
|
StateProvider,
|
|
VisibilityProvider,
|
|
ActionProvider,
|
|
} from "@json-render/react";
|
|
|
|
import { registry, Fallback, handlers as createHandlers } from "./registry";
|
|
|
|
// =============================================================================
|
|
// DashboardRenderer
|
|
// =============================================================================
|
|
|
|
type SetState = (
|
|
updater: (prev: Record<string, unknown>) => Record<string, unknown>,
|
|
) => void;
|
|
|
|
interface DashboardRendererProps {
|
|
spec: Spec | null;
|
|
state?: Record<string, unknown>;
|
|
setState?: SetState;
|
|
onStateChange?: (changes: Array<{ path: string; value: unknown }>) => void;
|
|
loading?: boolean;
|
|
}
|
|
|
|
// Fallback component for unknown types
|
|
const fallback: ComponentRenderer = ({ element }) => (
|
|
<Fallback type={element.type} />
|
|
);
|
|
|
|
export function DashboardRenderer({
|
|
spec,
|
|
state = {},
|
|
setState,
|
|
onStateChange,
|
|
loading,
|
|
}: DashboardRendererProps): ReactNode {
|
|
// Use refs so action handlers always see the latest state/setState
|
|
const stateRef = useRef(state);
|
|
const setStateRef = useRef(setState);
|
|
stateRef.current = state;
|
|
setStateRef.current = setState;
|
|
|
|
// Create ActionProvider-compatible handlers using getters so they
|
|
// always read the latest state/setState from refs
|
|
const actionHandlers = useMemo(
|
|
() =>
|
|
createHandlers(
|
|
() => setStateRef.current,
|
|
() => stateRef.current,
|
|
),
|
|
[],
|
|
);
|
|
|
|
if (!spec) return null;
|
|
|
|
return (
|
|
<StateProvider initialState={state} onStateChange={onStateChange}>
|
|
<VisibilityProvider>
|
|
<ActionProvider handlers={actionHandlers}>
|
|
<Renderer
|
|
spec={spec}
|
|
registry={registry}
|
|
fallback={fallback}
|
|
loading={loading}
|
|
/>
|
|
</ActionProvider>
|
|
</VisibilityProvider>
|
|
</StateProvider>
|
|
);
|
|
}
|