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 26, 2021
1 parent 36fbfd0 commit 632aed0
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 114 deletions.
94 changes: 27 additions & 67 deletions packages/core/__tests__/Form.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,9 @@ import render from './utils/RenderTest';

describe('Form', () => {
it('should render form elements', () => {
const { container } = render(
<>
<Input name="name" />
<Input multiline name="bio" />
</>,
);
const { container } = render(<Input name="name" />);

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

it('should load initial data inside form elements', () => {
Expand Down Expand Up @@ -100,12 +94,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 @@ -120,14 +111,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 @@ -139,12 +125,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 @@ -161,9 +145,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 @@ -207,15 +189,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 @@ -229,15 +206,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 @@ -251,14 +223,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 @@ -408,23 +373,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
27 changes: 9 additions & 18 deletions packages/core/__tests__/components/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,13 @@ import React, { useEffect, useRef, memo } from 'react';

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

interface Props<T> {
interface Props extends React.InputHTMLAttributes<HTMLInputElement> {
name: string;
label?: string;
multiline?: T;
}

type InputProps = JSX.IntrinsicElements['input'] & Props<false>;
type TextAreaProps = JSX.IntrinsicElements['textarea'] & Props<true>;

function Input({
name,
label,
multiline = false,
...rest
}: InputProps | TextAreaProps) {
const ref = useRef<HTMLInputElement | HTMLTextAreaElement>(null);
function Input({ name, label, ...rest }: Props) {
const ref = useRef<HTMLInputElement>(null);
const {
fieldName,
registerField,
Expand All @@ -28,7 +19,11 @@ function Input({

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

Expand All @@ -45,11 +40,7 @@ function Input({
<>
{label && <label htmlFor={fieldName}>{label}</label>}

{multiline ? (
<textarea onFocus={clearError} {...(props as TextAreaProps)} />
) : (
<input onFocus={clearError} {...(props as InputProps)} />
)}
<input onFocus={clearError} {...props} />

{error && <span>{error}</span>}
</>
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: RefForwardingComponent<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) : false;
},
[],
);

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: RefForwardingComponent<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
36 changes: 22 additions & 14 deletions packages/core/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import { DetailedHTMLProps, FormHTMLAttributes, FormEvent } from 'react';
import {
DetailedHTMLProps,
FormHTMLAttributes,
FormEvent,
RefObject,
} 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;
Expand All @@ -27,7 +34,7 @@ export interface UnformContext {
initialData: object;
errors: UnformErrors;
scopePath: string;
registerField<T>(field: UnformField<T>): void;
registerField<T, U>(field: UnformField<T, U>): void;
unregisterField: (name: string) => void;
clearFieldError: (fieldName: string) => void;
handleSubmit: (e?: FormEvent) => void;
Expand All @@ -39,14 +46,15 @@ type HTMLFormProps = DetailedHTMLProps<
FormHTMLAttributes<HTMLFormElement>,
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 | boolean;
getFieldError(fieldName: string): string | undefined;
setFieldError(fieldName: string, error: string): void;
clearField(fieldName: string): void;
getData(): object;
getFieldRef(fieldName: string): any;
getFieldRef(fieldName: string): RefObject<U> | undefined;
setData(data: object): void;
getErrors(): UnformErrors;
setErrors(errors: object): void;
Expand Down
3 changes: 1 addition & 2 deletions packages/core/lib/useField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useContext, useEffect, useMemo, useCallback } from 'react';
import dot from 'dot-object';

import FormContext from './Context';
import { UnformContext } from './types';

export default function useField(name: string) {
const {
Expand All @@ -13,7 +12,7 @@ export default function useField(name: string) {
unregisterField,
registerField,
clearFieldError,
} = useContext<UnformContext>(FormContext);
} = useContext(FormContext);

if (!name) {
throw new Error('You need to provide the "name" prop.');
Expand Down

0 comments on commit 632aed0

Please sign in to comment.