Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

Commit

Permalink
chore: Access field ref instead of current value
Browse files Browse the repository at this point in the history
Currently we used to pass the ref.current value to registerField
function, but with that Unform was referencing the current value
and no the reference itself and sometimes we were losing track
of the reference to get the field value.

Fixes #339.
  • Loading branch information
diego3g committed Jan 28, 2021
1 parent 9b9abc2 commit 633b272
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 118 deletions.
87 changes: 26 additions & 61 deletions packages/core/__tests__/Form.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ describe('Form', () => {

expect(!!container.querySelector('input[name=name]')).toBe(true)
})

it('should load initial data inside form elements', () => {
const { container } = render(<Input name="name" />, {
initialData: { name: 'Diego' },
Expand Down Expand Up @@ -94,12 +93,9 @@ describe('Form', () => {
})

it('should reset form data when reset helper is dispatched', () => {
const { getByTestId, getByLabelText } = render(
<>
<Input name="name" />
</>,
{ onSubmit: (_: any, { reset }: { reset: Function }) => reset() }
)
const { getByTestId, getByLabelText } = render(<Input name="name" />, {
onSubmit: (_: any, { reset }: { reset: Function }) => reset(),
})

getByLabelText('name').setAttribute('value', 'Diego')

Expand All @@ -114,14 +110,9 @@ describe('Form', () => {
tech: 'react',
}

const { getByTestId, getByLabelText } = render(
<>
<Input name="name" />
</>,
{
onSubmit: (_: any, { reset }: { reset: Function }) => reset(newData),
}
)
const { getByTestId, getByLabelText } = render(<Input name="name" />, {
onSubmit: (_: any, { reset }: { reset: Function }) => reset(newData),
})

getByLabelText('name').setAttribute('value', 'Diego')

Expand All @@ -133,12 +124,10 @@ describe('Form', () => {
it('should be able to have custom value parser', () => {
const submitMock = jest.fn()

const { getByTestId } = render(
<>
<CustomInputParse name="name" />
</>,
{ onSubmit: submitMock, initialData: { name: 'Diego' } }
)
const { getByTestId } = render(<CustomInputParse name="name" />, {
onSubmit: submitMock,
initialData: { name: 'Diego' },
})

fireEvent.submit(getByTestId('form'))

Expand All @@ -155,9 +144,7 @@ describe('Form', () => {

it('should be able to have custom value clearer', () => {
const { getByTestId, getByLabelText } = render(
<>
<CustomInputClear name="name" />
</>,
<CustomInputClear name="name" />,
{
onSubmit: (_: any, { reset }: { reset: Function }) => reset(),
initialData: { name: 'Diego' },
Expand Down Expand Up @@ -201,15 +188,10 @@ describe('Form', () => {
it('should be able to manually get field value', () => {
const formRef: RefObject<FormHandles> = { current: null }

render(
<>
<Input name="name" />
</>,
{
ref: formRef,
initialData: { name: 'John Doe' },
}
)
render(<Input name="name" />, {
ref: formRef,
initialData: { name: 'John Doe' },
})

if (formRef.current) {
const value = formRef.current.getFieldValue('name')
Expand All @@ -223,15 +205,10 @@ describe('Form', () => {
it('should be able to manually set field error', () => {
const formRef: RefObject<FormHandles> = { current: null }

const { getByText } = render(
<>
<Input name="name" />
</>,
{
onSubmit: (_: any, { reset }: { reset: Function }) => reset(),
ref: formRef,
}
)
const { getByText } = render(<Input name="name" />, {
onSubmit: (_: any, { reset }: { reset: Function }) => reset(),
ref: formRef,
})

act(() => {
if (formRef.current) {
Expand All @@ -245,14 +222,7 @@ describe('Form', () => {
it('should be able to manually get field error', async () => {
const formRef: RefObject<FormHandles> = { current: null }

render(
<>
<Input name="name" />
</>,
{
ref: formRef,
}
)
render(<Input name="name" />, { ref: formRef })

act(() => {
if (formRef.current) {
Expand Down Expand Up @@ -402,23 +372,18 @@ describe('Form', () => {
})

it('should be able to manually get field ref', () => {
const formRef: RefObject<FormHandles> = { current: null }
const formRef: RefObject<FormHandles<string, HTMLInputElement>> = {
current: null,
}

render(
<>
<Input name="name" />
</>,
{
ref: formRef,
}
)
render(<Input name="name" />, { ref: formRef })

if (formRef.current) {
const ref = formRef.current.getFieldRef('name')
const refNonExistent = formRef.current.getFieldRef('notexists')

expect((ref as HTMLInputElement).name).toBe('name')
expect(refNonExistent).toBe(false)
expect(ref?.current?.name).toBe('name')
expect(refNonExistent).toBeUndefined()
}
})

Expand Down
12 changes: 7 additions & 5 deletions packages/core/__tests__/components/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { useEffect, useRef, InputHTMLAttributes } from 'react'

import { useField } from '../../lib'

interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
interface Props extends InputHTMLAttributes<HTMLInputElement> {
name: string
label?: string
}

export function Input({ name, label, ...rest }: InputProps) {
export function Input({ name, label, ...rest }: Props) {
const ref = useRef<HTMLInputElement>(null)
const {
fieldName,
Expand All @@ -18,9 +18,11 @@ export function Input({ name, label, ...rest }: InputProps) {
} = useField(name)

useEffect(() => {
if (ref.current) {
registerField({ name: fieldName, ref: ref.current, path: 'value' })
}
registerField<string, HTMLInputElement>({
name: fieldName,
ref,
path: 'value',
})
}, [fieldName, registerField])

const props = {
Expand Down
10 changes: 6 additions & 4 deletions packages/core/__tests__/components/ObjectInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ export function ObjectInput({ name, label, ...rest }: InputProps) {
const { fieldName, registerField, defaultValue, error } = useField(name)

useEffect(() => {
registerField<InputValue>({
registerField<InputValue, HTMLInputElement>({
name: fieldName,
ref: inputRef.current,
ref: inputRef,
path: 'value',
setValue(ref: HTMLInputElement, value) {
ref.value = value.id
setValue(ref, value) {
if (ref.current) {
ref.current.value = value.id
}
},
})
}, [fieldName, registerField])
Expand Down
10 changes: 6 additions & 4 deletions packages/core/__tests__/utils/CustomInputClear.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ export function CustomInputClear({ name, label, ...rest }: Props) {

useEffect(() => {
if (ref.current) {
registerField({
registerField<string, HTMLInputElement>({
name: fieldName,
ref: ref.current,
ref,
path: 'value',
clearValue: (inputRef: HTMLInputElement) => {
inputRef.value = 'test'
clearValue: inputRef => {
if (inputRef.current) {
inputRef.current.value = 'test'
}
},
})
}
Expand Down
11 changes: 7 additions & 4 deletions packages/core/__tests__/utils/CustomInputParse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ export function CustomInputParse({ name, label, ...rest }: Props) {

useEffect(() => {
if (ref.current) {
registerField({
registerField<string, HTMLInputElement>({
name: fieldName,
ref: ref.current,
getValue: (currentRef: HTMLInputElement) =>
currentRef.value.concat('-test'),
ref,
getValue: currentRef => {
const inputValue = currentRef.current?.value ?? ''

return inputValue.concat('-test')
},
})
}
}, [fieldName, registerField])
Expand Down
22 changes: 9 additions & 13 deletions packages/core/lib/FormProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,42 +27,42 @@ const Form: ForwardRefRenderFunction<FormHandles, FormProps> = (
)

const getFieldValue = useCallback(({ ref, getValue, path }: UnformField) => {
if (getValue) {
if (getValue && ref) {
return getValue(ref)
}

return path && dot.pick(path, ref)
return path && dot.pick(path, ref.current)
}, [])

const setFieldValue = useCallback(
({ path, ref, setValue }: UnformField, value: any) => {
if (setValue) {
if (setValue && ref) {
return setValue(ref, value)
}

return path ? dot.set(path, value, ref as object) : false
return path && dot.set(path, value, ref.current)
},
[]
)

const clearFieldValue = useCallback(
({ clearValue, ref, path }: UnformField) => {
if (clearValue) {
if (clearValue && ref) {
return clearValue(ref, '')
}

return path && dot.set(path, '', ref as object)
return path && dot.set(path, '', ref.current)
},
[]
)

const reset = useCallback((data = {}) => {
fields.current.forEach(({ name, ref, path, clearValue }) => {
if (clearValue) {
if (clearValue && ref) {
return clearValue(ref, data[name])
}

return path && dot.set(path, data[name] ? data[name] : '', ref as object)
return path && dot.set(path, data[name] ? data[name] : '', ref.current)
})
}, [])

Expand Down Expand Up @@ -178,11 +178,7 @@ const Form: ForwardRefRenderFunction<FormHandles, FormProps> = (
getFieldRef(fieldName) {
const field = getFieldByName(fieldName)

if (!field) {
return false
}

return field.ref
return field?.ref
},
setData(data) {
return setData(data)
Expand Down
39 changes: 21 additions & 18 deletions packages/core/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,40 @@ import {
DetailedHTMLProps,
FormHTMLAttributes,
FormEvent,
RefObject,
ReactNode,
} from 'react'

interface BaseUnformField<T> {
interface BaseUnformField<T, U> {
name: string
ref?: any
setValue?: (ref: any, value: T) => void
clearValue?: (ref: any, newValue: T) => void
ref: RefObject<U>
setValue?: (ref: RefObject<U>, value: T) => void
clearValue?: (ref: RefObject<U>, newValue: T) => void
}

export interface PathUnformField<T> extends BaseUnformField<T> {
export interface PathUnformField<T, U> extends BaseUnformField<T, U> {
path: string
getValue?: undefined
}

export interface FunctionUnformField<T> extends BaseUnformField<T> {
export interface FunctionUnformField<T, U> extends BaseUnformField<T, U> {
path?: undefined
getValue: (ref: any) => T
getValue: (ref: RefObject<U>) => T
}

export type UnformField<T = any> = PathUnformField<T> | FunctionUnformField<T>
export type UnformField<T = any, U = any> =
| PathUnformField<T, U>
| FunctionUnformField<T, U>

export interface UnformErrors {
[key: string]: string | undefined
}

export interface UnformContext {
initialData: Record<string, unknown>
initialData: object
errors: UnformErrors
scopePath: string
registerField<T>(field: UnformField<T>): void
registerField<T = any, U = any>(field: UnformField<T, U>): void
unregisterField: (name: string) => void
clearFieldError: (fieldName: string) => void
handleSubmit: (e?: FormEvent) => void
Expand All @@ -45,18 +48,18 @@ type HTMLFormProps = DetailedHTMLProps<
HTMLFormElement
>

export interface FormHandles {
getFieldValue(fieldName: string): any
setFieldValue(fieldName: string, value: any): void | boolean
export interface FormHandles<T = any, U = any> {
getFieldValue(fieldName: string): T
setFieldValue(fieldName: string, value: T): void
getFieldError(fieldName: string): string | undefined
setFieldError(fieldName: string, error: string): void
clearField(fieldName: string): void
getData(): Record<string, unknown>
getFieldRef(fieldName: string): any
setData(data: Record<string, unknown>): void
getData(): object
getFieldRef(fieldName: string): RefObject<U> | undefined
setData(data: object): void
getErrors(): UnformErrors
setErrors(errors: Record<string, string>): void
reset(data?: Record<string, unknown>): void
setErrors(errors: object): void
reset(data?: object): void
submitForm(): void
}

Expand Down

0 comments on commit 633b272

Please sign in to comment.