Skip to content

Instantly share code, notes, and snippets.

@innocenzi
Last active June 28, 2022 18:27
Show Gist options
  • Save innocenzi/f5d1308e3fd14e6cc5e66beb295d7dce to your computer and use it in GitHub Desktop.
Save innocenzi/f5d1308e3fd14e6cc5e66beb295d7dce to your computer and use it in GitHub Desktop.
import { VisitOptions } from '../router'
import { useForm as useSlimeForm, UseFormBuilder, UseFormRule } from 'slimeform'
import { reactive, readonly, ref, UnwrapNestedRefs } from 'vue'
import { router } from '../router'
type SubmitFunctionParameters = Omit<VisitOptions, 'data'>
interface FormOptions<FormT extends {}> extends Omit<VisitOptions, 'data'> {
timeout?: number
/** Target URL. */
url: string
/** Actual fields. */
fields: FormT | UseFormBuilder<FormT>
/** Optional transformations before submission. */
transform?: <Transformed = any>(fields: UnwrapNestedRefs<FormT>) => Transformed
/** Verification rules. Applied **locally** before submission. */
rules?: UseFormRule<FormT>
/** Form key, used for the error bag and history state. */
key?: string | false
}
export function useForm<FormT extends {}>(options: FormOptions<FormT>) {
const { submitter, form, clearErrors, dirtyFields, isError, reset, status } = useSlimeForm({
rule: options.rules,
form: typeof options.fields === 'function'
? options.fields as UseFormBuilder<FormT>
: (() => options.fields) as UseFormBuilder<FormT>,
})
/** Whether the submission was recently successful. */
const recentlySuccessful = ref(false)
/** Whether the submission was recently failed. */
const recentlyFailed = ref(false)
/** Whether the submission is successful. */
const successful = ref(false)
/** Whether the form is being processed. */
const processing = ref(false)
const timeoutIds = {
recentlyFailed: undefined as ReturnType<typeof setTimeout> | undefined,
recentlySuccessful: undefined as ReturnType<typeof setTimeout> | undefined,
}
async function submit(overrides?: SubmitFunctionParameters) {
const { submit } = submitter(async({ form, clearErrors, status, reset }) => {
return await router.visit({
method: overrides?.method ?? 'POST',
...options,
...overrides,
data: options.transform?.(form) ?? form,
preserveState: overrides?.preserveState === undefined && options.method !== 'GET'
? true
: overrides?.preserveState,
events: {
before: (visit) => {
successful.value = false
recentlySuccessful.value = false
clearTimeout(timeoutIds.recentlySuccessful!)
clearTimeout(timeoutIds.recentlyFailed!)
clearErrors()
return options.events?.before?.(visit)
},
start: (context) => {
processing.value = true
return options.events?.start?.(context)
},
error: (errors) => {
Object.keys(errors).forEach((error) => (status as any)[error].setError(errors[error], true))
recentlyFailed.value = true
timeoutIds.recentlyFailed = setTimeout(() => recentlyFailed.value = false, options?.timeout ?? 5000)
return options.events?.error?.(errors)
},
success: (payload) => {
reset()
successful.value = true
recentlySuccessful.value = true
timeoutIds.recentlySuccessful = setTimeout(() => recentlySuccessful.value = false, options?.timeout ?? 5000)
return options.events?.success?.(payload)
},
after: (context) => {
processing.value = false
return options.events?.after?.(context)
},
},
})
}, {
enableVerify: false,
})
return await submit()
}
function abort() {
router.abort()
}
return reactive({
submit,
processing: readonly(processing),
successful: readonly(successful),
recentlyFailed: readonly(recentlyFailed),
recentlySuccessful: readonly(recentlySuccessful),
fields: form,
clearErrors,
dirtyFields,
hasErrors: isError,
reset,
status,
abort,
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment