@barbapapazes/vue-forms
TypeScript icon, indicating that this package has built-in type declarations

0.1.5 • Public • Published

vue-forms

[!NOTE] If you find this project useful, please consider supporting my work via GitHub Sponsors! 💜

npm version npm downloads

Streamline form handling in Vue with a straightforward and expressive API. Eliminate boilerplate code and concentrate on your form logic.

Installation

# ✨ Auto-detect
npx npm install @barbapapazes/vue-forms

# npm
npm install @barbapapazes/vue-forms

# yarn
yarn add @barbapapazes/vue-forms

# pnpm
pnpm install @barbapapazes/vue-forms

Usage

This package draws heavy inspiration from the useForm feature of Inertia.js.

To begin, import the useForm composable to instantiate a new form.

import { useForm } from '@barbapapazes/vue-forms'

Fields

You need to supply an object with the initial values of the form fields. Access them through the data object to bind them to the form inputs.

<script setup lang="ts">
import { useForm } from '@barbapapazes/vue-forms'

const { data } = useForm({
  email: '',
  password: '',
})
</script>

<template>
  <form>
    <input v-model="data.email" type="email">
    <input v-model="data.password" type="password">
    <button type="submit">
      Submit
    </button>
  </form>
</template>

Alternatively, you can wrap useForm with a reactive function to bypass the necessity of destructuring the useForm object. Read more about Vue.js composable functions.

<script setup lang="ts">
import { useForm } from '@barbapapazes/vue-forms'
import { reactive } from 'vue'

const form = reactive(useForm({
  email: '',
  password: '',
}))
</script>

<template>
  <form>
    <input v-model="form.data.email" type="email">
    <input v-model="form.data.password" type="password">
    <button type="submit">
      Submit
    </button>
  </form>
</template>

Validation

This library assumes that validation occurs on the server-side.

However, since we cannot predict the structure of a returned validation error, employ the transformErrorResponse hook to convert the error response into a format that maps to the form fields' errors.

Should your server return an error resembling this:

{
  "data": {
    "email": ["The email field is required."],
    "password": ["The password field is required."]
  }
}

Transform it as follows:

post('/login', {
  transformErrorResponse: (error) => {
    return {
      email: error.data.email?.[0],
      password: error.data.password?.[0],
    }
  }
})

This transformation maps the error messages to the corresponding fields, enabling display within the form. Without this transformation, errors cannot be displayed.

<template>
  <form @submit.prevent="submit">
    <input v-model="data.email" type="email">
    <span v-if="errors.email">{{ errors.email }}</span>

    <input v-model="data.password" type="password">
    <span v-if="errors.password">{{ errors.password }}</span>

    <button type="submit">
      Submit
    </button>
  </form>
</template>

Submitting

Once completed, you can submit the form to the server using several methods: submit, post, put, patch, and delete. The submit method is versatile and handles any HTTP method.

const { post } = useForm({
  email: '',
  password: '',
})

post('/login') // Sends a POST request to '/login' with a payload of { email: '', password: '' }

The useForm exposes additional references and methods:

  • processing is set to true while the form is submitting and reverts to false upon completion of the request (whether successful or not). This can be utilized to disable the submit button during submission.
  • recentlySuccessful is set to true following a successful submission of the form and resets to false after a brief delay. You can adjust the delay using the recentlySuccessfulTimeout option.
  • clearErrors clears the errors object. Specific errors can be cleared by passing the field name as an argument. For instance, clearErrors('email', 'password') clears the errors for both the email and password fields.
  • setError assigns an error. Pass the field name and error message as setError('email', 'The email field is required.'), or provide a partial error object like setError({ email: 'The email field is required.' }).
  • reset restores the form to its initial state, clearing both the data and errors objects.
<script setup lang="ts">
import { useForm } from '@barbapapazes/vue-forms'
import { reactive } from 'vue'

const form = reactive(useForm({
  name: 'John Doe',
  email: '',
}))

interface CreateUserResponse {
  data: {
    name: string
    email: string
  }
}

interface CreateUserValidationError {
  data: {
    errors: {
      message: string
      name?: string
      email?: string[]
    }
  }
}

function submit() {
  form.post<CreateUserResponse, CreateUserValidationError>('/form', {
    transformErrorResponse(error) {
      return {
        name: error.data.errors.name,
        email: error.data.errors.email?.[0],
      }
    },
    onSuccess() {
      form.reset()
    },
  })
}
</script>

<template>
  <h1> Vue Forms Playground </h1>

  <p v-if="form.recentlySuccessful">
    Form submitted successfully!
  </p>

  <form @submit.prevent="submit">
    <label>
      Name:
      <input v-model="form.data.name">
      <p> {{ form.errors.name }} </p>
    </label>

    <label>
      Email:
      <input v-model="form.data.email">
      <p> {{ form.errors.email }} </p>
    </label>

    <button
      type="submit"
      :disabled="form.processing"
    >
      Submit
    </button>
  </form>
</template>

Internally, the useForm composable employs the ofetch package. You can apply similar options as those available in the ofetch function to each method (e.g., interceptors to manage the usage of the XSRF-TOKEN cookie with onRequest).

form.post('/login', {
  baseURL: 'https://api.example.com',
  headers: {
    'Content-Type': 'application/json',
  },
})

TypeScript

The useForm composable leverages the provided inputs to infer the types of the data and errors objects.

You may also define types for the response from the server in the event of a successful submission or an error.

const form = reactive(useForm({
  email: '',
  password: '',
}))

// Successful response
interface CreateUserResponse {
  data: {
    name: string
    email: string
  }
}

// Error response
interface CreateUserValidationError {
  data: {
    errors: {
      message: string
      name?: string
      email?: string[]
    }
  }
}

function submit() {
  form.post<CreateUserResponse, CreateUserValidationError>('/api/login', {
    // eslint-disable-next-line node/handle-callback-err
    transformErrorResponse(error) {
      // `error` is typed using the `CreateUserValidationError` interface
    },
    onSuccess(response) {
      // `response` is typed using the `CreateUserResponse` interface
    },
  })
}

Development

Local development
  • Clone this repository
  • Install latest LTS version of Node.js
  • Enable Corepack using corepack enable
  • Install dependencies using pnpm install

After the local development environment is configured, navigate to the ./playground directory to launch the development server.

cd playground
pnpm dev # Start the Vite development server and a server API.

The playground simulates the library in a real-world context. It's a Vue 3 application utilizing the library from the src directory. There's no need to pre-build the library; it connects directly with Hot Module Replacement (HMR).

License

Published under the MIT license.

Sponsors

I would like to thank the following sponsors for supporting my work:

Sponsors

Readme

Keywords

Package Sidebar

Install

npm i @barbapapazes/vue-forms

Weekly Downloads

293

Version

0.1.5

License

MIT

Unpacked Size

25.5 kB

Total Files

19

Last publish

Collaborators

  • barbapapazes