Skip to content

Commit

Permalink
Merge pull request #136 from SeasonedSoftware/imperative-submit
Browse files Browse the repository at this point in the history
Imperative submit
  • Loading branch information
diogob authored Jan 4, 2023
2 parents 0821ae3 + 690d85a commit 9d1900c
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 2 deletions.
3 changes: 3 additions & 0 deletions apps/web/app/routes/examples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ export default function Component() {
<SidebarLayout.NavLink to={'/examples/forms/multiple-forms'}>
Multiple forms
</SidebarLayout.NavLink>
<SidebarLayout.NavLink to={'/examples/forms/imperative-submit'}>
Imperative submit
</SidebarLayout.NavLink>
<SidebarLayout.NavTitle>renderField</SidebarLayout.NavTitle>
<SidebarLayout.NavLink
to={'/examples/render-field/required-indicator'}
Expand Down
70 changes: 70 additions & 0 deletions apps/web/app/routes/examples/forms/imperative-submit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import hljs from 'highlight.js/lib/common'
import type {
ActionFunction,
LoaderFunction,
MetaFunction,
} from '@remix-run/node'
import { formAction } from '~/formAction'
import { z } from 'zod'
import Form from '~/ui/form'
import { metaTags } from '~/helpers'
import { makeDomainFunction } from 'domain-functions'
import Example from '~/ui/example'

const title = 'Imperative submit'
const description =
'In this example, we trigger a submit as soon as the user enters 4 characters.'

export const meta: MetaFunction = () => metaTags({ title, description })

const code = `const schema = z.object({ token: z.string().length(4) })
export default () => (
<Form schema={schema}>
{({ Field, Errors, submit }) => (
<>
<Field
name="token"
onChange={(ev: React.ChangeEvent<HTMLInputElement>) => {
if (ev.target.value.length === 4) submit()
}}
/>
<Errors />
</>
)}
</Form>
)`

const schema = z.object({
token: z.string().length(4),
})

export const loader: LoaderFunction = () => ({
code: hljs.highlight(code, { language: 'ts' }).value,
})

const mutation = makeDomainFunction(schema)(async (values) => values)

export const action: ActionFunction = async ({ request }) =>
formAction({ request, schema, mutation })

export default function Component() {
return (
<Example title={title} description={description}>
<Form schema={schema}>
{({ Field, Errors, submit }) => (
<>
<Field
name="token"
placeholder="Type 4 digits"
onChange={(ev: React.ChangeEvent<HTMLInputElement>) => {
if (ev.target.value.length === 4) submit()
}}
/>
<Errors />
</>
)}
</Form>
</Example>
)
}
20 changes: 20 additions & 0 deletions apps/web/tests/examples/forms/imperative-submit.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { expect } from '@playwright/test'
import { test } from 'tests/setup/tests'

const route = '/examples/forms/imperative-submit'

test('With JS enabled', async ({ example }) => {
const { page } = example
const token = example.field('token')

await page.goto(route)

await token.input.first().type('123')
expect(page.locator('#action-data > pre')).toBeHidden()

await token.input.first().type('4')

await example.expectData({
token: '1234',
})
})
15 changes: 13 additions & 2 deletions packages/remix-forms/src/createForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ type BaseFormProps = {
type BaseFormPropsWithHTMLAttributes =
React.FormHTMLAttributes<HTMLFormElement> & BaseFormProps

type FormComponent = React.ForwardRefExoticComponent<BaseFormProps>
type FormComponent = React.ForwardRefExoticComponent<
React.PropsWithoutRef<BaseFormProps> & React.RefAttributes<HTMLFormElement>
>

type Field<SchemaType> = {
shape: ZodTypeAny
Expand Down Expand Up @@ -77,6 +79,7 @@ type Children<Schema extends SomeZodObject> = (
Errors: ComponentOrTagName<'div'>
Error: ComponentOrTagName<'div'>
Button: ComponentOrTagName<'button'>
submit: () => void
} & UseFormReturn<z.infer<Schema>, any>,
) => React.ReactNode

Expand Down Expand Up @@ -226,6 +229,13 @@ function createForm({
form.handleSubmit(() => submit(event.target))(event)
}

const formRef = React.useRef<HTMLFormElement>(null)
const doSubmit = () => {
formRef.current?.dispatchEvent(
new Event('submit', { cancelable: true, bubbles: true }),
)
}

const Field = React.useMemo(
() =>
createField<ObjectFromSchema<Schema>>({
Expand Down Expand Up @@ -336,6 +346,7 @@ function createForm({
Errors,
Error,
Button,
submit: doSubmit,
...form,
}),
(child) => {
Expand Down Expand Up @@ -460,7 +471,7 @@ function createForm({

return (
<FormProvider {...form}>
<Component method={method} onSubmit={onSubmit} {...props}>
<Component ref={formRef} method={method} onSubmit={onSubmit} {...props}>
{beforeChildren}
{customChildren ?? defaultChildren()}
</Component>
Expand Down

0 comments on commit 9d1900c

Please sign in to comment.