Skip to content

Stop infinite renders caused by circular $ref object properties#5118

Open
heath-freenome wants to merge 2 commits into
mainfrom
fix-3907
Open

Stop infinite renders caused by circular $ref object properties#5118
heath-freenome wants to merge 2 commits into
mainfrom
fix-3907

Conversation

@heath-freenome

@heath-freenome heath-freenome commented Jun 7, 2026

Copy link
Copy Markdown
Member

Reasons for making this change

Fixes #3907 and #4262

  • Adds RJSF_REF_CYCLE_KEY ('__rjsf_ref_cycle') to detect and terminate self-referential schemas that would otherwise render infinitely.
  • In resolveAllReferences, when a $ref is encountered that's already in the recurseList AND we're resolving in simple (non-resolveAnyOfOrOneOfRefs) mode, the schema is tagged with __rjsf_ref_cycle: true instead of silently returning the unresolved $ref. This only fires in a direct object-property context (!resolveAnyOfOrOneOfRefs), where rendering is unconditional and cycles are genuinely infinite. Array-items and anyOf/oneOf contexts remain untagged since they are data-driven and terminate naturally.
  • SchemaField checks _schema[RJSF_REF_CYCLE_KEY] after its useCallback hook (to satisfy React rules of hooks) and renders a CyclicSchemaField instead of recursing. CyclicSchemaField renders the CyclicSchemaExpandTemplate, which shows the user a message and an expand button to load the next cycle break.
  • hashForSchema now filters out keys starting with RJSF_REF_KEY before hashing so that internal cycle-tracking metadata does not affect the computed hash.
  • CyclicSchemaExpandTemplate is implemented for all theme packages: antd, chakra-ui, daisyui, fluentui-rc, mantine, mui, primereact, react-bootstrap, semantic-ui, and shadcn, each using their native UI library components.
  • Updated snapshots for the new tests

Checklist

  • I'm updating documentation
  • I'm adding or updating code
    • I've added and/or updated tests. I've run npx nx run-many --target=build --exclude=@rjsf/docs && npm run test:update to update snapshots, if needed.
    • I've updated docs if needed
    • I've updated the changelog with a description of the PR
  • I'm adding a new feature
    • I've updated the playground with an example use of the feature

Preview

Screen.Recording.2026-06-07.at.8.00.42.PM.mov

@heath-freenome heath-freenome requested a review from nickgros June 7, 2026 20:32
@heath-freenome heath-freenome force-pushed the fix-3907 branch 2 times, most recently from a681ff1 to 2957503 Compare June 7, 2026 20:51
Fixes #3907, #4262 and #3826

- Adds RJSF_REF_CYCLE_KEY ('__rjsf_ref_cycle') to detect and terminate
  self-referential schemas that would otherwise render infinitely.
- In resolveAllReferences, when a $ref is encountered that's already in the
  recurseList AND we're resolving in simple (non-resolveAnyOfOrOneOfRefs) mode,
  the schema is tagged with __rjsf_ref_cycle: true instead of silently returning
  the unresolved $ref. This only fires in a direct object-property context
  (!resolveAnyOfOrOneOfRefs), where rendering is unconditional and cycles are
  genuinely infinite. Array-items and anyOf/oneOf contexts remain untagged since
  they are data-driven and terminate naturally.
- SchemaField checks _schema[RJSF_REF_CYCLE_KEY] after its useCallback hook
  (to satisfy React rules of hooks) and renders a CyclicSchemaField instead
  of recursing. CyclicSchemaField renders the CyclicSchemaExpandTemplate, which
  shows the user a message and an expand button to load the next cycle break.
- hashForSchema now filters out keys starting with RJSF_REF_KEY before hashing
  so that internal cycle-tracking metadata does not affect the computed hash.
- CyclicSchemaExpandTemplate is implemented for all theme packages: antd,
  chakra-ui, daisyui, fluentui-rc, mantine, mui, primereact, react-bootstrap,
  semantic-ui, and shadcn, each using their native UI library components.
- Fixed buttonId in all CyclicSchemaExpandTemplate implementations to use
  fieldPathId[ID_KEY] (the $id string) instead of fieldPathId (the object).
  Button id format is now `${fieldPathId.$id}-button` (e.g. `root_child_child-button`),
  dropping the redundant `-${name}-` segment.
- Added SchemaField test verifying that clicking the expand button renders the
  next level of the cycle, finding the button by its correct DOM id.
- Updated FormSnap snapshot to reflect the corrected button id.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@x0k

x0k commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
  1. In the video, clicking the button adds two fields.
  2. If the initial data is {"child":{"child":{"child":{"child":{}}}}}, only two levels are rendered, plus an expand button.

In SJSF, I tried to solve this problem by generating a schemaLocation for each field in order to detect cycles at the form level (rather than in utils), but so far I have not found a simple way to guarantee correct JSON Pointer generation in all cases (then/else, dependencies, etc.).

@heath-freenome

Copy link
Copy Markdown
Member Author
  1. In the video, clicking the button adds two fields.

    1. If the initial data is {"child":{"child":{"child":{"child":{}}}}}, only two levels are rendered, plus an expand button.

In SJSF, I tried to solve this problem by generating a schemaLocation for each field in order to detect cycles at the form level (rather than in utils), but so far I have not found a simple way to guarantee correct JSON Pointer generation in all cases (then/else, dependencies, etc.).

Hi @x0k... Yeah it's not until we encounter the second instance of the same key that the cycle is detected, hence the information showing up 2x for each cycle since the marker is on that second item. I've been debating doing something with the recursion such that upon the cycle detection, I can somehow go back to the first instance and mark that one instead. I may spend some more time on that once this gets merged

@x0k

x0k commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

hashForSchema now filters out keys starting with RJSF_REF_KEY before hashing so that internal cycle-tracking metadata does not affect the computed hash.

FYI, if you use Symbols, such keys are automatically ignored by JSON.stringify.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Recursive json-schema definition causes the browser to hang and eventually stack overflow

2 participants