Skip to content

Commit

Permalink
fix: fix Field allways passes a undefined className to custom component
Browse files Browse the repository at this point in the history
  • Loading branch information
myNameIsDu committed Sep 14, 2023
1 parent da58b29 commit 0d21ff3
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 36 deletions.
31 changes: 16 additions & 15 deletions packages/formik/src/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,19 @@ export interface FieldConfig<V = any> {
* Field component to render. Can either be a string like 'select' or a component.
*/
component?:
| string
| React.ComponentType<FieldProps<V>>
| React.ComponentType
| React.ForwardRefExoticComponent<any>;
| string
| React.ComponentType<FieldProps<V>>
| React.ComponentType
| React.ForwardRefExoticComponent<any>;

/**
* Component to render. Can either be a string e.g. 'select', 'input', or 'textarea', or a component.
*/
as?:
| React.ComponentType<FieldProps<V>['field']>
| string
| React.ComponentType
| React.ForwardRefExoticComponent<any>;
| React.ComponentType<FieldProps<V>['field']>
| string
| React.ComponentType
| React.ForwardRefExoticComponent<any>;

/**
* Render prop (works like React router's <Route render={props =>} />)
Expand Down Expand Up @@ -72,10 +72,12 @@ export interface FieldConfig<V = any> {
innerRef?: (instance: any) => void;
}

export type FieldAttributes<T> = { className?: string; } & GenericFieldHTMLAttributes &
export type FieldAttributes<T> = {
className?: string;
} & GenericFieldHTMLAttributes &
FieldConfig<T> &
T & {
name: string,
name: string;
};

export type FieldHookConfig<T> = GenericFieldHTMLAttributes & FieldConfig<T>;
Expand Down Expand Up @@ -141,7 +143,6 @@ export function Field({
children,
as: is, // `as` is reserved in typescript lol
component,
className,
...props
}: FieldAttributes<any>) {
const {
Expand Down Expand Up @@ -205,14 +206,14 @@ export function Field({
const { innerRef, ...rest } = props;
return React.createElement(
component,
{ ref: innerRef, ...field, ...rest, className },
{ ref: innerRef, ...field, ...rest },
children
);
}
// We don't pass `meta` for backwards compat
return React.createElement(
component,
{ field, form: formik, ...props, className },
{ field, form: formik, ...props },
children
);
}
Expand All @@ -224,10 +225,10 @@ export function Field({
const { innerRef, ...rest } = props;
return React.createElement(
asElement,
{ ref: innerRef, ...field, ...rest, className },
{ ref: innerRef, ...field, ...rest },
children
);
}

return React.createElement(asElement, { ...field, ...props, className }, children);
return React.createElement(asElement, { ...field, ...props }, children);
}
72 changes: 51 additions & 21 deletions packages/formik/test/Field.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,14 @@ describe('Field / FastField', () => {

describe('renders an <input /> by default', () => {
it('<Field />', () => {
const className = 'field-custom'
const { container } = renderForm(<Field name="name" className={className} />);
const className = 'field-custom';
const { container } = renderForm(
<Field name="name" className={className} />
);
expect(container.querySelectorAll('input')).toHaveLength(1);
expect(container.querySelector(`.${className}`)?.getAttribute('value')).toEqual('jared')
expect(
container.querySelector(`.${className}`)?.getAttribute('value')
).toEqual('jared');
});

it('<FastField />', () => {
Expand All @@ -112,22 +116,6 @@ describe('Field / FastField', () => {
});
});

describe('renders an <input /> with className', () => {
it('<Field />', () => {
const className = 'field-custom'
const { container } = renderForm(<Field name="name" className={className} />);
expect(container.querySelectorAll(`.${className}`)).toHaveLength(1)
expect(container.querySelector(`.${className}`)?.getAttribute('value')).toEqual('jared')
});

it('<FastField />', () => {
const className = 'field-custom'
const { container } = renderForm(<FastField name="name" className={className} />);
expect(container.querySelectorAll(`.${className}`)).toHaveLength(1)
expect(container.querySelector(`.${className}`)?.getAttribute('value')).toEqual('jared')
});
});

describe('receives { field, form, meta } props and renders element', () => {
it('<Field />', () => {
let injected: FieldProps[] = [];
Expand Down Expand Up @@ -222,7 +210,7 @@ describe('Field / FastField', () => {
});

describe('children', () => {
cases('renders a child element with component', () => {
it('renders a child element with component', () => {
const { container } = renderForm(
<Field name="name" component="select">
<option value="Jared" label={TEXT} />
Expand All @@ -233,7 +221,7 @@ describe('Field / FastField', () => {
expect(container.querySelectorAll('option')).toHaveLength(2);
});

cases('renders a child element with as', () => {
it('renders a child element with as', () => {
const { container } = renderForm(
<Field name="name" as="select">
<option value="Jared" label={TEXT} />
Expand Down Expand Up @@ -604,6 +592,48 @@ describe('Field / FastField', () => {

expect(getProps().field.value).toBe('Binding');
});

describe('renders an <input /> with className', () => {
it('<Field />', () => {
const className = 'field-custom';
const { container } = renderForm(
<Field name="name" className={className} />
);
expect(container.querySelectorAll(`.${className}`)).toHaveLength(1);
expect(
container.querySelector(`.${className}`)?.getAttribute('value')
).toEqual('jared');
});

it('<FastField />', () => {
const className = 'field-custom';
const { container } = renderForm(
<FastField name="name" className={className} />
);
expect(container.querySelectorAll(`.${className}`)).toHaveLength(1);
expect(
container.querySelector(`.${className}`)?.getAttribute('value')
).toEqual('jared');
});
});
cases(
"render custom component and doesn't overwrite className",
renderField => {
const { container } = renderField({
children: ({ form, field, ...reset }) => (
/**
* @see https://github.com/jaredpalmer/formik/issues/3883
* ensure when Field or FastField component don't review classNames,
* they won't pass {className:undefined} to custom component
*
*/
<input name="name" className="custom-class" {...reset} />
),
});

expect(container.querySelector('.custom-class')).toBeTruthy();
}
);
});

// @todo Deprecated
Expand Down

0 comments on commit 0d21ff3

Please sign in to comment.