Skip to content

Commit

Permalink
feat: 🎸 Clears errors on an input when the value changes
Browse files Browse the repository at this point in the history
BREAKING CHANGE: 🧨 Clears errors by default on value change. Must pass
`clearErrorsOnChange: false` to `useInertiaInput` to disable behavior
  • Loading branch information
aviemet committed May 21, 2024
1 parent 19f25d7 commit aaca443
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 7 deletions.
6 changes: 3 additions & 3 deletions src/Form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const Form = <TForm extends NestedObject>({
if(resetAfterSubmit || (resetAfterSubmit !== false && async === true)) {
form.reset()
}
if(onSuccess) onSuccess(contextValueObject())
onSuccess?.(contextValueObject())
},
})
}
Expand All @@ -99,11 +99,11 @@ const Form = <TForm extends NestedObject>({

// Callbacks
useEffect(() => {
if(onChange) onChange(contextValueObject())
onChange?.(contextValueObject())
}, [form.data])

useEffect(() => {
if(onError) onError(contextValueObject())
onError?.(contextValueObject())
}, [form.errors])

return (
Expand Down
25 changes: 22 additions & 3 deletions src/useInertiaInput/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,26 @@ import { useForm } from '../Form'
import { useNestedAttribute } from '../NestedFields'
import inputStrategy, { type InputStrategy } from './inputStrategy'
import { type NestedObject } from '../useInertiaForm'
import { useEffect } from 'react'

interface UseInertiaInputProps {
name: string
model?: string
errorKey?: string
strategy?: InputStrategy
clearErrorsOnChange?: boolean
}

/**
* Returns form data and input specific methods to use with an input.
*/
const useInertiaInput = <T = number|string, TForm = NestedObject>({ name, model, errorKey, strategy = inputStrategy }: UseInertiaInputProps) => {
const useInertiaInput = <T = number|string, TForm = NestedObject>({
name,
model,
errorKey,
strategy = inputStrategy,
clearErrorsOnChange = true,
}: UseInertiaInputProps) => {
const form = useForm<TForm>()

let usedModel = model ?? form.model
Expand All @@ -23,17 +31,28 @@ const useInertiaInput = <T = number|string, TForm = NestedObject>({ name, model,
usedModel += `.${nested}`
} catch(e) {}


const { inputName, inputId } = strategy(name, usedModel)

const value = form.getData(inputName) as T
const usedErrorKey = errorKey ?? inputName
const error = form.getError(usedErrorKey)

// Clear errors when input value changes
useEffect(() => {
if(!clearErrorsOnChange || !error) return
form.clearErrors(usedErrorKey)
}, [value])

return {
form,
inputName: inputName,
inputId,
value: form.getData(inputName) as T,
value,
setValue: (value: T) => {
return form.setData(inputName, value)
},
error: form.getError(errorKey ?? inputName),
error,
}
}

Expand Down
29 changes: 29 additions & 0 deletions tests/TestInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react'
import { useInertiaInput } from '../src'

interface TestInputProps {
name: string
model?: string
clearErrorsOnChange?: boolean
}

const TestInput = ({ name, model, clearErrorsOnChange = true }: TestInputProps) => {
const { inputName, inputId, value, setValue } = useInertiaInput<string>({
name,
model,
clearErrorsOnChange,
})

return (
<input
type="text"
role="input"
name={ inputName }
id={ inputId }
value={ value }
onChange={ e => setValue(e.target.value) }
/>
)
}

export default TestInput
2 changes: 1 addition & 1 deletion tests/useInertiaForm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ describe('unsetData', () => {
})
})

// Test not strictly necessary since we don't override setError
// Tests not strictly necessary since we don't override setError
describe('setError', () => {
it('should set errors by key', () => {
const { result } = renderHook(() => useInertiaForm(initialData))
Expand Down
65 changes: 65 additions & 0 deletions tests/useInertiaInput.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react'
import { fireEvent, render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import { Form, useForm } from '../src/Form'
import TestInput from './TestInput'

const FormContextTest = () => {
const { errors, setError } = useForm()

const handleClick = e => {
e.preventDefault()
setError('name', 'Error')
}

return (
<>
<button onClick={ handleClick } role="button" aria-label="error" />
<div data-testid="errors">{ JSON.stringify(errors) }</div>
</>
)
}

describe ('useInertiaInput', () => {
describe('With clearErrorsOnChange = true', () => {
it('clears errors on an input when the value changes ', () => {
render(
<Form role="form" to="/" data={ { name: '' } } remember={ false }>
<FormContextTest />
<TestInput name="name" />
</Form>,
)

const errorButton = screen.getByRole('button', { name: 'error' })
const input = screen.getByRole('input')

fireEvent.click(errorButton)
expect(screen.getByTestId('errors')).toHaveTextContent('{"name":"Error"}')

fireEvent.change(input, { target: { value: 'something' } })
expect(screen.getByTestId('errors')).toHaveTextContent('{}')
})

})

describe('With clearErrorsOnChange = true', () => {
it('doesn\'t clear errors on an input when the value changes', () => {
render(
<Form role="form" to="/" data={ { name: '' } } remember={ false }>
<FormContextTest />
<TestInput name="name" clearErrorsOnChange={ false } />
</Form>,
)

const errorButton = screen.getByRole('button', { name: 'error' })
const input = screen.getByRole('input')

fireEvent.click(errorButton)
expect(screen.getByTestId('errors')).toHaveTextContent('{"name":"Error"}')

fireEvent.change(input, { target: { value: 'something' } })
expect(screen.getByTestId('errors')).toHaveTextContent('{"name":"Error"}')
})

})
})

0 comments on commit aaca443

Please sign in to comment.