Feature request for a `createForm` function or another way to wrap `useForm`
innocenzi opened this issue · 9 comments
Hey, I'm building a library where I have a custom form helper that looks exactly like SlimeForm. I think SlimeForm is doing a very good job, and I would love to depend on it for my own form helper.
The issue is that I need to override the submit
function with custom logic (this is for a routing library). I wanted to provide my own useForm
that would defer the logic to SlimeForm, but I can't get the typings because they're not exported.
I was initially going to PR to export them, but I thought it would be nice if SlimeForm provided some API to generate a pre-configured useForm
hook.
I'd happily PR that but I wanted to see if that would be accepted. I'm not sure about the API, but I'm thinking something like that:
// Somewhere in my own library
import { router } from './router'
import type { VisitOptions } from './types'
import { createForm } from 'slimeform'
export const useForm = createForm({
submit: ({ form }) => async (overrides?: VisitOptions) => {
// Here I implement my custom submit method, and this method is returned
return await router.visit({
method: overrides.method ?? 'POST',
data: form,
...overrides
})
},
})
// Somewhere in userland, using my library
import { useForm } from 'my-library'
const { form, submit } = useForm({
form: () => ({
email: '',
password: '',
})
})
// These parameters are from `VisitOptions`
submit({ url: '/register', method: 'POST' })
Basically, every property from the object given to createForm
would be a callback that return a function, and that function would be provided to the user. The callback would have form
and status
given to it.
It seems a bit complicated, but it's also flexible. If you have another idea on how I could achieve my goal, I'm open to it too!
First of all, thanks for your suggestion.
-
You said SlimeForm doesn't export some types, if you need it, you can specify which types need to be exported, I'd love to see a PR to complete type export.
-
If the function of
createForm
is just to call back thesubmit
function, you can do it by wrappinguseForm
in a secondary way.
So I hopecreateForm
is not just for calling back a function, if you have a more complete and useful design/concept forcreateForm
, please share it with me. -
I'm already planning to improve the existing
onSubmit
function, in the future you should be able to implement it like this.
const { submit, submitting, /* ... */ } = onSubmit(MySubmit({ url: '/register', method: 'POST' }))
submitting.value // true / false
submit() // callback
Hey, thanks for your answer!
You said SlimeForm doesn't export some types, if you need it, you can specify which types need to be exported, I'd love to see a PR to complete type export.
I was going to do just that, and I will do that if we can't find a good API for my purpose.
So I hope
createForm
is not just for calling back a function, if you have a more complete and useful design/concept forcreateForm
, please share it with me.
Essentially, my idea was that createForm
accepted an object, and each of the properties of that object would be returned by the resulting useForm
. In the example I gave, I only created a submit
function, but anyone with other needs could create something else. For instance, say I want a transform
function that would mutate the form
before submitting, I could do that:
export const useForm = createForm({
transform: () => () => {/* implementation */},
submit: () => () => {/* implementation */},
})
I do agree that this could be a bit complex for no reason though. Personally I would only like to be able to override that submit
method on my side, but I thought of that API to be flexible.
I'm already planning to improve the existing
onSubmit
function
If that allows me, in a wrapper function, to override the submit behavior, that'd be all I need!
If you export some desired type, such as UseFormParam
, you can append the functionality you need like this:
import { useForm } from 'slimeform'
import { router } from './router'
import type { VisitOptions } from './types'
import type { UseFormParam } from 'slimeform'
export function myUseForm<FormT extends {}>(param: UseFormParam<FormT>): UseFormReturn<FormT> {
const { form, ...leftovers } = useForm(param)
return {
form,
...leftovers,
// Custom submit function
submit: async (overrides?: VisitOptions) => {
// Here I implement my custom submit method, and this method is returned
return await router.visit({
method: overrides.method ?? 'POST',
data: form,
...overrides,
})
},
}
}
It's just as simple and flexible
I created an issue about enhancing onSubmit
Issue: #40
Released v0.6.0, it has a great submitter
function and exports all types, hope you will like it.
Perfect timing, I'll try that asap, thanks a lot!
Hey, so I'm trying to make this submitter work. It's almost there, but a few things:
- The
submitter
function expects a function that performs the submitting, but I need to allow parameters on my exposedsubmit
function. For this reason I had to create asubmit
function myself, that wraps a call tosubmitter
and then calls thesubmitter
'ssubmit
function. Was this intended?
async function submit(overrides?: SubmitFunctionParameters) {
const { submit } = submitter(async({ form, clearErrors, status, reset }) => {
return await router.visit({
...overrides,
// ...
})
}, {
enableVerify: false,
})
return await submit()
}
-
For some reason, when
enableVerify
istrue
, I can only submit once, the subsequent submits won't be triggered. I don't need that feature, but I wanted to tell you because that seems like a bug. -
form
,dirtyFields
andstatus
are not reactive it seems - it's only updated upon submission. I'm investigating that to see if it's a bug.
Here is my complete wrapper in case you spot any obvious mistake in it that I missed: https://gist.github.com/innocenzi/f5d1308e3fd14e6cc5e66beb295d7dce
By the way, thanks for the amazing work! ❤️
About these questions:
Question 1
The submitter
function was not designed to be used in this way, perhaps you can read the documentation to understand its intent.
import { mySubmitForm } from './myFetch.ts'
const { _, submitter } = useForm(/* ... */)
// Wrap the generic submission code and use it later
const { submit, submitting } = submitter(mySubmitForm({ url: '/register', method: 'POST' }))
In general, the submit
function is bound to the form's @submit.prevent
, so it doesn't really need to be called with parameters.
But you can provide various arguments when you create it as in the code above.
Question 2 and 3
I tried reading the code you provided, but I can't see what is causing these errors, I need a runnable minimal reproduction and an issue.
Note: They are currently working fine in 🚀 slimeform-playground.
I'll investigate and report back, thanks!