Skip to content

refactor(core): convert MultiSchemaField AnyOfField from class to function component#5073

Draft
Copilot wants to merge 5 commits into
mainfrom
copilot/convert-multischemafield-to-function-component
Draft

refactor(core): convert MultiSchemaField AnyOfField from class to function component#5073
Copilot wants to merge 5 commits into
mainfrom
copilot/convert-multischemafield-to-function-component

Conversation

Copilot AI commented May 22, 2026

Copy link
Copy Markdown

AnyOfField in MultiSchemaField.tsx was the last remaining class component in the fields directory. This converts it to a function component following the same patterns used by BooleanField, ObjectField, and LayoutMultiSchemaField.

Key changes

  • State initializationuseReducer lazy initializer replaces the constructor; computes retrievedOptions and initial selectedOption in one pass on mount. All state is captured in a named AnyOfFieldState<S> type.
  • componentDidUpdate → derived state during render — Previous prop values are stored inside the reducer state (prevOptions, prevFormData, prevFieldPathIdId). When a prop changes, updated currentRetrieved/currentSelected locals are computed synchronously during render and a single SYNC_PROPS action is dispatched — no useEffect or useRef required. React re-renders immediately with the new state, preserving the original cascade ordering (options recomputed first, then selectedOption re-evaluated against the freshly retrieved options).
  • useReducer — All connected state is managed by a single anyOfFieldReducer<S> pure function defined outside the component, with a discriminated AnyOfFieldAction<S> union (SYNC_PROPS | SELECT_OPTION) making every transition explicit and easy to reason about.
  • onOptionChange — converted to useCallback with stable primitive deps; dispatches SELECT_OPTION.
  • getFieldId() — replaced by an inline computed fieldId string.
  • Dep stabilityfieldPathId.$id and fieldPathId.path are extracted as primitive locals before hooks to avoid unnecessary callback re-runs from object reference churn.
// Before
class AnyOfField extends Component<FieldProps, AnyOfFieldState> {
  constructor(props) { /* retrieve options, compute initial selectedOption */ }
  componentDidUpdate(prevProps, prevState) { /* re-derive state on options/formData change */ }
  onOptionChange = (option) => { this.setState(..., () => onChange(...)) }
  render() { ... }
}

// After
function AnyOfField(props: FieldProps) {
  const [state, dispatch] = useReducer<AnyOfFieldState<S>, AnyOfFieldAction<S>>(anyOfFieldReducer, ...);
  // previous prop values stored in reducer state; derived state computed during render (no useEffect needed)
  // dispatch({ type: 'SYNC_PROPS', ...computedValues }) when tracked props change
  const onOptionChange = useCallback((option) => { dispatch({ type: 'SELECT_OPTION', ... }); onChange(...) }, [...]);
  return ...;
}

Copilot AI and others added 3 commits May 22, 2026 19:31
…schemafield-to-function-component

Co-authored-by: nickgros <17580037+nickgros@users.noreply.github.com>
Comment on lines +54 to +57
const [{ selectedOption, retrievedOptions }, setState] = useState<{
selectedOption: number;
retrievedOptions: S[];
}>(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Either create a type for this (similar to the removed AnyOfFieldState), or split this state into two atoms. Also, consider if using a reducer is appropriate.

Consider the rules of hooks and "you might not need an effect". Are there better alternatives than using these refs and this useEffect?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in febfd4a.

  • Restored the AnyOfFieldState<S> named type for the combined state.
  • Replaced useEffect + three useRefs with the React "adjusting state when a prop changes" pattern: three useState atoms (prevOptions, prevFormData, prevFieldPathIdId) track previous prop values, and the derivation logic runs synchronously during render — no useEffect needed. currentRetrieved/currentSelected locals carry the up-to-date values through the rest of the render body.

…nder; restore AnyOfFieldState type

Agent-Logs-Url: https://github.com/rjsf-team/react-jsonschema-form/sessions/ec7336ed-fc8e-4e27-9256-928995d3bd4a

Co-authored-by: nickgros <17580037+nickgros@users.noreply.github.com>

Copilot AI commented May 22, 2026

Copy link
Copy Markdown
Author

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • www.google-analytics.com
    • Triggering command: /home/REDACTED/work/_temp/ghcca-node/node/bin/node node /home/REDACTED/work/react-jsonschema-form/react-jsonschema-form/node_modules/.bin/nx run-many --target=build (dns block)
    • Triggering command: /opt/hostedtoolcache/node/24.15.0/x64/bin/node /opt/hostedtoolcache/node/24.15.0/x64/bin/node /home/REDACTED/work/react-jsonschema-form/react-jsonschema-form/node_modules/nx/src/project-graph/plugins/isolation/plugin-worker /tmp/plugin4411-2-401.602115.sock /home/REDACTED/work/react-jsonschema-form/react-jsonschema-form/node_modules/nx/src/plugins/project-json/build-nodes/project-json is-ci (dns block)
    • Triggering command: /opt/hostedtoolcache/node/24.15.0/x64/bin/node /opt/hostedtoolcache/node/24.15.0/x64/bin/node /home/REDACTED/work/react-jsonschema-form/react-jsonschema-form/node_modules/nx/src/project-graph/plugins/isolation/plugin-worker /tmp/plugin4411-1-398.022779.sock /home/REDACTED/work/react-jsonschema-form/react-jsonschema-form/node_modules/nx/src/plugins/package-json is-ci (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from nickgros May 22, 2026 19:54
Comment on lines +72 to +73
// Track previous prop values in state so we can derive state changes during render,
// avoiding the need for a useEffect + refs to replicate componentDidUpdate behaviour.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Track previous prop values in state so we can derive state changes during render,
// avoiding the need for a useEffect + refs to replicate componentDidUpdate behaviour.
// Track previous prop values in state so we can derive state changes during render

// avoiding the need for a useEffect + refs to replicate componentDidUpdate behaviour.
const [prevOptions, setPrevOptions] = useState(options);
const [prevFormData, setPrevFormData] = useState(formData);
const [prevFieldPathIdId, setPrevFieldPathIdId] = useState(fieldPathIdId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This is a lot of connected state variables and it is hard to reason about changing them. Create a reducer function and use useReducer.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 35dad30.

All four connected state atoms are now managed by a single useReducer:

  • AnyOfFieldState<S> — expanded to include prevOptions, prevFormData, and prevFieldPathIdId alongside selectedOption and retrievedOptions
  • AnyOfFieldAction<S> — a discriminated union of SYNC_PROPS (dispatched during render when tracked props change) and SELECT_OPTION (dispatched from onOptionChange)
  • anyOfFieldReducer<S> — a pure function defined outside the component; SYNC_PROPS sets all fields from the pre-computed action payload, SELECT_OPTION updates only selectedOption and retrievedOptions

…OfFieldState/AnyOfFieldAction types

Agent-Logs-Url: https://github.com/rjsf-team/react-jsonschema-form/sessions/4033026f-26f4-4a1d-9af6-374fd021900d

Co-authored-by: nickgros <17580037+nickgros@users.noreply.github.com>

Copilot AI commented May 22, 2026

Copy link
Copy Markdown
Author

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • www.google-analytics.com
    • Triggering command: /home/REDACTED/work/_temp/ghcca-node/node/bin/node node node_modules/.bin/nx run-many --target=build (dns block)
    • Triggering command: /opt/hostedtoolcache/node/24.15.0/x64/bin/node /opt/hostedtoolcache/node/24.15.0/x64/bin/node /home/REDACTED/work/react-jsonschema-form/react-jsonschema-form/node_modules/nx/src/project-graph/plugins/isolation/plugin-worker /tmp/plugin4326-2-357.196315.sock /home/REDACTED/work/react-jsonschema-form/react-jsonschema-form/node_modules/nx/src/plugins/project-json/build-nodes/project-json (dns block)
    • Triggering command: /opt/hostedtoolcache/node/24.15.0/x64/bin/node /opt/hostedtoolcache/node/24.15.0/x64/bin/node /home/REDACTED/work/react-jsonschema-form/react-jsonschema-form/node_modules/nx/src/project-graph/plugins/isolation/plugin-worker /tmp/plugin4326-1-353.851298.sock /home/REDACTED/work/react-jsonschema-form/react-jsonschema-form/node_modules/nx/src/plugins/package-json (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from nickgros May 22, 2026 20:19
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.

2 participants