Skip to content

Commit

Permalink
Merge pull request #126 from SeasonedSoftware/revalidation-after-subm…
Browse files Browse the repository at this point in the history
…ission

Revalidate (or at least clean) server errors on input change
  • Loading branch information
diogob authored Dec 13, 2022
2 parents fea4a39 + da2d681 commit 0a8a9a3
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 10 deletions.
27 changes: 27 additions & 0 deletions apps/web/tests/examples/actions/field-error.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
import { test, testWithoutJS, expect } from 'tests/setup/tests'

const route = '/examples/actions/field-error'
test('With JS enabled clear server error on the client', async ({ example }) => {
const { email, password, button, page } = example

await page.goto(route)

// Render
await example.expectField(email)
await example.expectField(password)
await expect(button).toBeEnabled()

// Make generate server error
await email.input.fill('foo@bar.com')
await example.expectValid(email)
await password.input.fill('123456')
await example.expectValid(password)

// Submit form
button.click()
await expect(button).toBeDisabled()

// Show field error
await example.expectError(email, 'Email already taken')

// Submit valid form
await email.input.fill('john@doe.com')
await example.expectValid(email)
})

test('With JS enabled', async ({ example }) => {
const { email, password, button, page } = example
Expand Down
24 changes: 15 additions & 9 deletions packages/remix-forms/src/createForm.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as React from 'react'
import type { SomeZodObject, z, ZodTypeAny } from 'zod'
import type { SomeZodObject, TypeOf, z, ZodTypeAny } from 'zod'
import type {
ComponentOrTagName,
FormSchema,
KeysOfStrings,
ObjectFromSchema,
} from './prelude'
import { objectFromSchema, mapObject } from './prelude'
import { objectFromSchema, mapObject, browser } from './prelude'
import type {
UseFormReturn,
FieldError,
Expand Down Expand Up @@ -262,9 +262,9 @@ function createForm({

const fieldErrors = (key: keyof SchemaType) => {
const message = (formErrors[key] as unknown as FieldError)?.message
return (message && [message]) || (errors && errors[key])
return browser() ? (message && [message]) : (errors && errors[key])
}
const firstErroredField = Object.keys(schemaShape).find(
const firstErroredField = () => Object.keys(schemaShape).find(
(key) => fieldErrors(key)?.length,
)
const makeField = (key: string) => {
Expand Down Expand Up @@ -292,7 +292,7 @@ function createForm({
label: (labels && labels[key]) || inferLabel(String(key)),
options: required ? fieldOptions : fieldOptionsPlusEmpty(),
errors: fieldErrors(key),
autoFocus: key === firstErroredField || key === autoFocusProp,
autoFocus: key === firstErroredField() || key === autoFocusProp,
value: defaultValues[key],
hidden:
hiddenFields && Boolean(hiddenFields.find((item) => item === key)),
Expand Down Expand Up @@ -322,7 +322,7 @@ function createForm({
const { name } = child.props
const field = makeField(name)

const autoFocus = firstErroredField
const autoFocus = firstErroredField()
? field?.autoFocus
: child.props.autoFocus ?? field?.autoFocus

Expand Down Expand Up @@ -418,10 +418,16 @@ function createForm({
}, [])

React.useEffect(() => {
if (firstErroredField) {
Object.keys(errors).forEach((key) => {
form.setError(
key as Path<TypeOf<Schema>>,
{ type: 'custom', message: (errors[key] as string[]).join(", ") }
)
})
if (firstErroredField()) {
try {
form.setFocus(firstErroredField as Path<SchemaType>)
} catch {}
form.setFocus(firstErroredField() as Path<SchemaType>)
} catch { }
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [errorsProp, unparsedActionData])
Expand Down
6 changes: 5 additions & 1 deletion packages/remix-forms/src/prelude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,9 @@ function parseDate(value?: Date | string) {
return date
}

export { objectFromSchema, mapObject, parseDate }
function browser(): boolean {
return typeof document === 'object'
}

export { objectFromSchema, mapObject, parseDate, browser }
export type { FormSchema, ObjectFromSchema, ComponentOrTagName, KeysOfStrings }

0 comments on commit 0a8a9a3

Please sign in to comment.