Skip to content

Commit fb09a09

Browse files
committed
fix: fix Field allways passes a undefined className to custom component
1 parent da58b29 commit fb09a09

File tree

3 files changed

+72
-36
lines changed

3 files changed

+72
-36
lines changed

.changeset/real-wolves-cough.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'formik': patch
3+
---
4+
5+
fix Field allways passes a undefined className to custom component

packages/formik/src/Field.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@ export interface FieldConfig<V = any> {
2222
* Field component to render. Can either be a string like 'select' or a component.
2323
*/
2424
component?:
25-
| string
26-
| React.ComponentType<FieldProps<V>>
27-
| React.ComponentType
28-
| React.ForwardRefExoticComponent<any>;
25+
| string
26+
| React.ComponentType<FieldProps<V>>
27+
| React.ComponentType
28+
| React.ForwardRefExoticComponent<any>;
2929

3030
/**
3131
* Component to render. Can either be a string e.g. 'select', 'input', or 'textarea', or a component.
3232
*/
3333
as?:
34-
| React.ComponentType<FieldProps<V>['field']>
35-
| string
36-
| React.ComponentType
37-
| React.ForwardRefExoticComponent<any>;
34+
| React.ComponentType<FieldProps<V>['field']>
35+
| string
36+
| React.ComponentType
37+
| React.ForwardRefExoticComponent<any>;
3838

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

75-
export type FieldAttributes<T> = { className?: string; } & GenericFieldHTMLAttributes &
75+
export type FieldAttributes<T> = {
76+
className?: string;
77+
} & GenericFieldHTMLAttributes &
7678
FieldConfig<T> &
7779
T & {
78-
name: string,
80+
name: string;
7981
};
8082

8183
export type FieldHookConfig<T> = GenericFieldHTMLAttributes & FieldConfig<T>;
@@ -141,7 +143,6 @@ export function Field({
141143
children,
142144
as: is, // `as` is reserved in typescript lol
143145
component,
144-
className,
145146
...props
146147
}: FieldAttributes<any>) {
147148
const {
@@ -205,14 +206,14 @@ export function Field({
205206
const { innerRef, ...rest } = props;
206207
return React.createElement(
207208
component,
208-
{ ref: innerRef, ...field, ...rest, className },
209+
{ ref: innerRef, ...field, ...rest },
209210
children
210211
);
211212
}
212213
// We don't pass `meta` for backwards compat
213214
return React.createElement(
214215
component,
215-
{ field, form: formik, ...props, className },
216+
{ field, form: formik, ...props },
216217
children
217218
);
218219
}
@@ -224,10 +225,10 @@ export function Field({
224225
const { innerRef, ...rest } = props;
225226
return React.createElement(
226227
asElement,
227-
{ ref: innerRef, ...field, ...rest, className },
228+
{ ref: innerRef, ...field, ...rest },
228229
children
229230
);
230231
}
231232

232-
return React.createElement(asElement, { ...field, ...props, className }, children);
233+
return React.createElement(asElement, { ...field, ...props }, children);
233234
}

packages/formik/test/Field.test.tsx

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,14 @@ describe('Field / FastField', () => {
100100

101101
describe('renders an <input /> by default', () => {
102102
it('<Field />', () => {
103-
const className = 'field-custom'
104-
const { container } = renderForm(<Field name="name" className={className} />);
103+
const className = 'field-custom';
104+
const { container } = renderForm(
105+
<Field name="name" className={className} />
106+
);
105107
expect(container.querySelectorAll('input')).toHaveLength(1);
106-
expect(container.querySelector(`.${className}`)?.getAttribute('value')).toEqual('jared')
108+
expect(
109+
container.querySelector(`.${className}`)?.getAttribute('value')
110+
).toEqual('jared');
107111
});
108112

109113
it('<FastField />', () => {
@@ -112,22 +116,6 @@ describe('Field / FastField', () => {
112116
});
113117
});
114118

115-
describe('renders an <input /> with className', () => {
116-
it('<Field />', () => {
117-
const className = 'field-custom'
118-
const { container } = renderForm(<Field name="name" className={className} />);
119-
expect(container.querySelectorAll(`.${className}`)).toHaveLength(1)
120-
expect(container.querySelector(`.${className}`)?.getAttribute('value')).toEqual('jared')
121-
});
122-
123-
it('<FastField />', () => {
124-
const className = 'field-custom'
125-
const { container } = renderForm(<FastField name="name" className={className} />);
126-
expect(container.querySelectorAll(`.${className}`)).toHaveLength(1)
127-
expect(container.querySelector(`.${className}`)?.getAttribute('value')).toEqual('jared')
128-
});
129-
});
130-
131119
describe('receives { field, form, meta } props and renders element', () => {
132120
it('<Field />', () => {
133121
let injected: FieldProps[] = [];
@@ -222,7 +210,7 @@ describe('Field / FastField', () => {
222210
});
223211

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

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

605593
expect(getProps().field.value).toBe('Binding');
606594
});
595+
596+
describe('renders an <input /> with className', () => {
597+
it('<Field />', () => {
598+
const className = 'field-custom';
599+
const { container } = renderForm(
600+
<Field name="name" className={className} />
601+
);
602+
expect(container.querySelectorAll(`.${className}`)).toHaveLength(1);
603+
expect(
604+
container.querySelector(`.${className}`)?.getAttribute('value')
605+
).toEqual('jared');
606+
});
607+
608+
it('<FastField />', () => {
609+
const className = 'field-custom';
610+
const { container } = renderForm(
611+
<FastField name="name" className={className} />
612+
);
613+
expect(container.querySelectorAll(`.${className}`)).toHaveLength(1);
614+
expect(
615+
container.querySelector(`.${className}`)?.getAttribute('value')
616+
).toEqual('jared');
617+
});
618+
});
619+
cases(
620+
"render custom component and doesn't overwrite className",
621+
renderField => {
622+
const { container } = renderField({
623+
children: ({ form, field, ...reset }) => (
624+
/**
625+
* @see https://github.com/jaredpalmer/formik/issues/3883
626+
* ensure when Field or FastField component don't review classNames,
627+
* they won't pass {className:undefined} to custom component
628+
*
629+
*/
630+
<input name="name" className="custom-class" {...reset} />
631+
),
632+
});
633+
634+
expect(container.querySelector('.custom-class')).toBeTruthy();
635+
}
636+
);
607637
});
608638

609639
// @todo Deprecated

0 commit comments

Comments
 (0)