[!NOTE] If you find this project useful, please consider supporting my work via GitHub Sponsors! 💜
Streamline form handling in Vue with a straightforward and expressive API. Eliminate boilerplate code and concentrate on your form logic.
# ✨ 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
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'
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>
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>
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 totrue
while the form is submitting and reverts tofalse
upon completion of the request (whether successful or not). This can be utilized to disable the submit button during submission. -
recentlySuccessful
is set totrue
following a successful submission of the form and resets tofalse
after a brief delay. You can adjust the delay using therecentlySuccessfulTimeout
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 assetError('email', 'The email field is required.')
, or provide a partial error object likesetError({ 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',
},
})
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
},
})
}
Local development
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).
Published under the MIT license.
I would like to thank the following sponsors for supporting my work: