renderer.tsx 1.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
  1. import React, { type ReactNode, useMemo } from "react";
  2. import {
  3. Renderer,
  4. StateProvider,
  5. VisibilityProvider,
  6. ActionProvider,
  7. ValidationProvider,
  8. createStandardActionHandlers,
  9. type Spec,
  10. } from "@json-render/react-native";
  11. import { registry } from "./registry";
  12. // =============================================================================
  13. // AppRenderer
  14. // =============================================================================
  15. interface AppRendererProps {
  16. spec: Spec | null;
  17. loading?: boolean;
  18. }
  19. export function AppRenderer({ spec, loading }: AppRendererProps): ReactNode {
  20. // Seed the StateProvider with any initial state from the spec.
  21. // Memoize so we only pick up the state from the first render of this spec
  22. // (otherwise re-renders during streaming would keep resetting the state).
  23. // NOTE: This hook must be called before any early return to satisfy Rules of Hooks.
  24. const initialState = useMemo(
  25. () => spec?.state ?? {},
  26. // Re-seed when the spec root changes (new generation)
  27. // eslint-disable-next-line react-hooks/exhaustive-deps
  28. [spec?.root],
  29. );
  30. if (!spec) return null;
  31. return (
  32. <StateProvider initialState={initialState}>
  33. <VisibilityProvider>
  34. <ActionProvider handlers={createStandardActionHandlers()}>
  35. <ValidationProvider>
  36. <Renderer spec={spec} registry={registry} loading={loading} />
  37. </ValidationProvider>
  38. </ActionProvider>
  39. </VisibilityProvider>
  40. </StateProvider>
  41. );
  42. }