0

I want to implement a form that is made of base fields (e.g.: product name, price and type), then type-specific fields depending on the type selected.

The solution seemed straightfoward at first (see this codesandbox), since I managed to render different components based on the type found in the current product state.

However, because these components are being rendered conditionally while using hooks internally such as useEffect, they are breaking the Rules of Hooks and causing the following error when I change from one product type to another:

Should have a queue. This is likely a bug in React. Please file an issue.

Warning: React has detected a change in the order of Hooks called by TypeSpecificForm. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks (...)

   Previous render            Next render
   ------------------------------------------------------
1. useState                   useEffect
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    in TypeSpecificForm (at ProductForm.js:36)
    in div (at ProductForm.js:14)
    in ProductForm (at App.js:8)
    in App (at index.js:15)
    in StrictMode (at index.js:14)

enter image description here

What is the correct way to render these partial fields dynamically?

Fappaz
  • 3,033
  • 3
  • 28
  • 39
  • Here's how I'd do this: https://codesandbox.io/s/react-subform-issue-forked-yrpllf?file=/ProductForm.js (store an array of fields as strings, use that to render additional fields) –  Aug 09 '22 at 08:37
  • @ChrisG I agree that this is a totally acceptable approach when the dynamic forms are made of a predictable list of fields. However for cases forms required a more sophisticated UI with unpredictable components, it seems to be less complex to implement their own component, which is what I was trying to achieve in my example. – Fappaz Aug 09 '22 at 11:20

1 Answers1

0

As I suspected, the solution was something rather silly.

Instead of returning the render function for the dynamic field...

function TypeSpecificForm({ product, onChange }) {
  const productType = productTypes[product.type];
  if (!productType?.renderForm) return null;

  return productType.renderForm({ product, onChange });
}

...I called it as a React Node component like so:

function TypeSpecificForm({ product, onChange }) {
  const productType = productTypes[product.type];
  if (!productType?.renderForm) return null;
  const Component = productType.renderForm

  return <Component product={product} onChange={onChange} />
}

Now React no longer complains about breaking the Rules of Hooks and everything works as expected.

I remember however seeing something on the React docs that these 2 approaches could be interchangeable, which clearly they are not. I'll update my answer once I find the exact root cause for this issue.

Edit: I still couldn't find anything on the official docs that highlights the differences between rendering components via a render function vs JSX components, but I did come across an interesting discussion about this very subject.

Fappaz
  • 3,033
  • 3
  • 28
  • 39