Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Field allways passes a undefined className to custom component #3884

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/real-wolves-cough.md
@@ -0,0 +1,5 @@
---
'formik': patch
---

fix Field allways passes a undefined className to custom component
31 changes: 16 additions & 15 deletions packages/formik/src/Field.tsx
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
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