inertiajs / inertia

Inertia.js lets you quickly build modern single-page React, Vue and Svelte apps using classic server-side routing and controllers.
https://inertiajs.com
MIT License
6.3k stars 423 forks source link

values prop not honored by useForm #1780

Closed skills-up closed 3 months ago

skills-up commented 8 months ago

I'm using a generic component for Create/Update forms. The difference between the two is values parameter passed to Update form.

const GenericForm: React.FC<GenericFormProps> = ({
  fields,
  values = null,
  ...
}) => {
  const defaultValues = {};
  for (let key of Object.keys(fields)) {
    defaultValues[key] = getDefaultValueForType(fields[key]);
  }
  const { data, setData, post, put, processing, errors, reset } = useForm(values || defaultValues);
}

Now, this is called in the following manner for the two purposes: Create Form

<GenericForm fields={formFields}/>

Update Form

<GenericForm fields={formFields} values={currentValues}/>

However, when the form is rendered, the data always contains the defaultValues and never the values. While this is OK for Create Form, it means losing the existing value for Update Form.

skills-up commented 8 months ago

Also,

I've tried the following alternative for populating defaultValues object:

  const defaultValues = {};
  for (let key of Object.keys(fields)) {
    defaultValues[key] = values ? values[key] : getDefaultValueForType(fields[key]);
  }

Log shows the defaultValues is updated by the data within values object, but data for form remains the same.

skills-up commented 8 months ago

Further, if I try to use setDefaults, like:

if (values) {
  setDefaults(values);
}

then it results in Too many rerenders; whereas, wrapping it in a useEffect results in the original behavior mentioned in this issue.

Hasan-Mir commented 8 months ago

This issue isn’t specific to Inertia; it’s a characteristic of React’s useState. The useForm hook uses the initial form data passed to it to set up its internal state. This state is only initialized once when the component is first rendered. If you try to update the form data from outside the component by passing different values props, it won’t affect the internal state of the useForm hook after it’s been initialized.

If you want to do so, you need to write a useEffect for it to update the data whenever the props gets a new value:

useEffect(() => {
   setData(values)
}, [values])

However, here you need to use useMemo for the object that's passed to GenericForm as the values prop in the parent component of GenericForm which renders it. Alternatively, you can use JSON.stringify(values) as the dependency for useEffect:

useEffect(() => {
   setData(values)
}, [JSON.stringify(values)])

Another possible solution is to create a key based on the values prop and pass that key to your GenericForm component. This will force React to unmount and remount your component, effectively resetting the useForm hook with the new values:

<GenericForm key={JSON.stringify(currentValues)} fields={formFields} values={currentValues}/>
derrickreimer commented 3 months ago

Good pointers @Hasan-Mir. Closing this one since this is the expected behavior for state management in React.