Search code examples
vue.jsasync-awaitvuejs3formkit

[Vue + FormKit]: Load Asynchronous Data into a Schema Generated Form


TL;DR Is there a way to load asynchronous data into <FormKitSchema>?

I'm trying to fetch async data and load it into a FormKit form generated from a JSON schema (<FormKitSchema>). I can't seem to find an example and I've tried several ways to do it, but haven't had any luck.

I understand the value property on an input element will only ever be used when initially rendered, which is cool. However, I can't seem to fetch the data first and then conditionally load the <FormKitSchema> afterwards (using v-if did nothing to help). It appears the rendering must be executed with the initial component loading or nothing shows up.

Example: formComponent.vue (<FormComponent>)

<template>
  <FormKitSchema :schema="schema" :data="asyncData"/>
</template>

<script setup>
import {reactive, ref} from 'vue';

const getData = async () => {
  return new Promise((resolve) => {    
    setTimeout(() => {
      resolve({name: "How do I get this into the form???"})
    }, 1500)
  })
};

const asyncData = reactive({
  userData: await getData(), // Using 'await' here seems to prevent the form from ever loading
  submit: async (formData, node) => {
    console.log("submit: ", {formData, node});
  }
})

const schema = [
  {
    $cmp: 'FormKit',
    props: {
      type: 'form',
      id: 'form',
      onSubmit: '$submit',
      plugins: '$plugins',
      actions: false,
    },
    children: [
      {
        $formkit: 'text',
        name: 'fullname',
        label: '*Full Name',
        placeholder: 'Full Name',
        value: "$userData.name",
        validation: 'required'
      },
      {
        $formkit: 'submit',
        label: 'Save',
        disabled: '$get(form).state.valid !== true'
      }
    ]
  }
]
</script>

Solution

  • Turns out this isn't a problem or issue with FormKit nor FormKitSchema. It's an issue with using await inside Vue's reactive() function.

    In order to use a promise resolution value (i.e. userData: await getData()) in the initial reactive() argument object of a component (like in the formComponent.vue example), the calling/parent component must wrap them in a <Suspense> tag. Otherwise, Vue appears to simply not render the component at all, which is why there were no errors and nothing showed up.

    From the Vue.js docs on Suspense... A component with async setup() must be nested in a <Suspense> in order to be rendered.

    Therefore, the formComponent.vue code in the question is okay. Just needed to add a <Suspense> tag to the template anywhere the <FormComponent> is used. For example...

    Example: parentView.vue

    <Suspense>
      <FormComponent/>
    </Suspense>
    

    NOTE: As of this writing, the <Suspense> element is experimental and may change.