unovue / radix-vue

Vue port of Radix UI Primitives. An open-source UI component library for building high-quality, accessible design systems and web apps.
https://radix-vue.com
MIT License
3.73k stars 233 forks source link

[Bug]: Radix Primitive: Form Data Clears on Dialog Close #1037

Closed jeremiah-olisa closed 5 months ago

jeremiah-olisa commented 5 months ago

Environment

System:
    OS: Windows 11 10.0.22631
    CPU: (8) x64 Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz
    Memory: 4.25 GB / 15.81 GB
  Binaries:
    Node: 18.18.2 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.19 - ~\AppData\Roaming\npm\yarn.CMD
    npm: 9.8.1 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Chromium (126.0.2592.68)
    Internet Explorer: 11.0.22621.3527
  npmPackages:
    nuxt: ^3.12.2 => 3.12.2 
    radix-vue: ^1.8.3 => 1.8.3 
    shadcn-nuxt: ^0.10.4 => 0.10.4 
    vue: ^3.4.29 => 3.4.29

Link to minimal reproduction

https://stackblitz.com/edit/nuxt-starter-ggrskg?file=README.md

Steps to reproduce

  1. Wrap a form inside a shadcn dialog component.
  2. Open the dialog and enter data into the form fields.
  3. Close the dialog.
  4. Reopen the dialog.

Describe the bug

Reproduction

https://stackblitz.com/edit/nuxt-starter-ggrskg?file=README.md

Describe the bug

Description

Issue Description:

When using a form wrapped inside a shadcn dialog component, the form data clears every time the dialog is closed and reopened. This behavior is not desired as it results in users losing their input data if the dialog is accidentally closed or if they navigate away and come back to the dialog.

Steps to Reproduce:

  1. Wrap a form inside a shadcn dialog component.
  2. Open the dialog and enter data into the form fields.
  3. Close the dialog.
  4. Reopen the dialog.

Expected Behavior:

The form should retain its data when the dialog is closed and reopened.

Actual Behavior:

The form data clears each time the dialog is closed and reopened.

Proposed Solution:

Manage the form's state outside the dialog component to ensure that the form data persists. Here is an example implementation:

FormWrapper.vue
<script lang="ts" setup>
import { type HTMLAttributes } from "vue";
const props = defineProps<{
  class?: HTMLAttributes["class"];
  isLoading?: boolean;
  id: string;
  label?: string;
  required?: boolean;
}>();
</script>

<template>
  <FormField v-slot="{ componentField }" :name="id">
    <FormItem>
      <FormLabel :class="{ 'required-label': required }" v-if="label" :for="id">
        {{ label }}
      </FormLabel>
      <FormControl>
        <slot :componentField="componentField" />
      </FormControl>
      <FormMessage />
    </FormItem>
  </FormField>
</template>
FormDialog.vue
<template>
  <Dialog>
    <DialogTrigger as-child>
      <Button variant="default">Edit</Button>
    </DialogTrigger>
    <DialogContent
      class="rounded-lg sm:max-w-[525px] md:max-w-[725px] lg:max-w-[825px] max-h-[95dvh]"
    >
      <DialogHeader>
        <DialogTitle>Personal data</DialogTitle>
        <DialogDescription>
          Please update the necessary customer information and submit
        </DialogDescription>
      </DialogHeader>
      <div class="p-2 overflow-y-auto">
        <form
          @submit.prevent="onSubmit"
          class="grid md:grid-cols-2 gap-4 py-4 h-[60dvh]"
        >
          <FormWrapper
            v-slot="{ componentField }"
            id="firstName"
            required
            label="First Name"
          >
            <Input
              v-model="formData.firstName"
              placeholder=""
              type="text"
              auto-capitalize="none"
              auto-complete="off"
              auto-correct="off"
              :disabled="isLoading"
              id="firstName"
              v-bind="componentField"
            />
          </FormWrapper>
          <FormWrapper
            v-slot="{ componentField }"
            id="lastName"
            required
            label="Last Name"
          >
            <Input
              v-model="formData.lastName"
              placeholder=""
              type="text"
              auto-capitalize="none"
              auto-complete="off"
              id="lastName"
              auto-correct="off"
              :disabled="isLoading"
              v-bind="componentField"
            />
          </FormWrapper>
          <FormWrapper
            v-slot="{ componentField }"
            id="phoneNumber"
            required
            label="Phone Number"
          >
            <Input
              v-model="formData.phoneNumber"
              placeholder=""
              type="text"
              auto-capitalize="none"
              auto-complete="off"
              auto-correct="off"
              :disabled="isLoading"
              id="phoneNumber"
              v-bind="componentField"
            />
          </FormWrapper>
          <FormWrapper
            v-slot="{ componentField }"
            id="address"
            label="Address (Optional)"
          >
            <Input
              v-model="formData.address"
              placeholder=""
              type="text"
              auto-capitalize="none"
              auto-complete="off"
              auto-correct="off"
              :disabled="isLoading"
              id="address"
              v-bind="componentField"
            />
          </FormWrapper>
          <FormWrapper
            v-slot="{ componentField }"
            id="gender"
            required
            label="Gender"
          >
            <Input
              v-model="formData.gender"
              placeholder=""
              type="text"
              auto-capitalize="none"
              auto-complete="off"
              id="gender"
              auto-correct="off"
              :disabled="isLoading"
              v-bind="componentField"
            />
          </FormWrapper>
          <FormWrapper
            v-slot="{ componentField }"
            id="natureOfBusiness"
            required
            label="Nature of Business"
          >
            <Input
              v-model="formData.natureOfBusiness"
              placeholder=""
              type="text"
              id="natureOfBusiness"
              auto-capitalize="none"
              auto-complete="off"
              auto-correct="off"
              :disabled="isLoading"
              v-bind="componentField"
            />
          </FormWrapper>
        </form>
      </div>
      <DialogFooter>
        <DialogClose as-child>
          <Button id="dialogClose" variant="destructive" type="button">
            Close
          </Button>
        </DialogClose>
        <Button
          :loading="isLoading"
          @click="onSubmit"
          loading-text="Creating..."
          type="submit"
        >
          Update Customer
        </Button>
      </DialogFooter>
    </DialogContent>
  </Dialog>
</template>

<script>
export default {
  data() {
    return {
      isLoading: false,
      formData: {
        firstName: '',
        lastName: '',
        phoneNumber: '',
        address: '',
        gender: '',
        natureOfBusiness: ''
      }
    };
  },
  methods: {
    onSubmit() {
      this.isLoading = true;
      // Handle form submission logic here
      // After submission, you can reset the form if needed
      // For example: this.resetForm();
    },
    resetForm() {
      this.formData = {
        firstName: '',
        lastName: '',
        phoneNumber: '',
        address: '',
        gender: '',
        natureOfBusiness: ''
      };
    }
  }
};
</script>

This also happens on this form: https://stackblitz.com/edit/nuxt-starter-ggrskg?file=README.md

System Info

System:
    OS: Windows 11 10.0.22631
    CPU: (8) x64 Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz
    Memory: 4.25 GB / 15.81 GB
  Binaries:
    Node: 18.18.2 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.19 - ~\AppData\Roaming\npm\yarn.CMD
    npm: 9.8.1 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Chromium (126.0.2592.68)
    Internet Explorer: 11.0.22621.3527
  npmPackages:
    nuxt: ^3.12.2 => 3.12.2 
    radix-vue: ^1.8.3 => 1.8.3 
    shadcn-nuxt: ^0.10.4 => 0.10.4 
    vue: ^3.4.29 => 3.4.29

Expected behavior

No response

Context & Screenshots (if applicable)

No response

zernonia commented 5 months ago

Hey @jeremiah-olisa .. this doesn't looks like a radix-vue issue to me. The Form data was removed because the internal state of Form was clear when unmounted, this is expected.

And as the concept of shadcn-vue, feel free to modify and wrap the component in your own way. 😁

jeremiah-olisa commented 5 months ago

Is there a unmount prop for dialog, that keeps it mounted??