Skip to content

Commit 96280d3

Browse files
authored
refactor: react 18 internal upgrade (#3817)
`useReducer().dispatch` appears to be async now where it used to be synchronous, leading to many broken tests. I have replaced usage of it with equivalent synchronous code.
1 parent 274c306 commit 96280d3

17 files changed

+156
-219
lines changed

.changeset/twenty-lions-tickle.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'formik': patch
3+
---
4+
5+
Updated internal types to support React 18.

package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
{
22
"name": "formik-project",
33
"private": true,
4+
"resolutions": {
5+
"shelljs": "0.8.5",
6+
"typescript": "^4.0.0"
7+
},
48
"devDependencies": {
59
"@changesets/changelog-github": "^0.2.7",
610
"@changesets/cli": "^2.10.3",
711
"@playwright/test": "^1.34.3",
8-
"@types/jest": "^26.0.14",
12+
"@types/jest": "^25.0.0",
913
"husky": "^4.3.0",
1014
"lint-staged": "^10.4.0",
1115
"prettier": "^2.1.2",

packages/formik-native/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
},
3535
"devDependencies": {
3636
"@react-native-community/eslint-config": "^0.0.5",
37-
"@types/react": "^16.9.55",
37+
"@types/react": "^18.2.7",
3838
"@types/react-native": "^0.63.32",
3939
"react": "^18.2.0",
4040
"react-native": "https://github.com/expo/react-native/archive/sdk-33.0.0.tar.gz",

packages/formik/package.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,19 @@
4848
"lodash-es": "^4.17.21",
4949
"react-fast-compare": "^2.0.1",
5050
"tiny-warning": "^1.0.2",
51-
"tslib": "^1.10.0"
51+
"tslib": "^2.0.0"
5252
},
5353
"devDependencies": {
54-
"@testing-library/react": "^11.1.0",
54+
"@testing-library/react": "^14.0.0",
5555
"@types/hoist-non-react-statics": "^3.3.1",
5656
"@types/lodash": "^4.14.119",
57-
"@types/react": "^16.9.55",
58-
"@types/react-dom": "^16.9.9",
57+
"@types/react": "^18.2.7",
58+
"@types/react-dom": "^18.2.4",
5959
"@types/warning": "^3.0.0",
6060
"@types/yup": "^0.24.9",
6161
"just-debounce-it": "^1.1.0",
62-
"react": "^17.0.1",
63-
"react-dom": "^17.0.1",
62+
"react": "^18.2.0",
63+
"react-dom": "^18.2.0",
6464
"tsdx": "^0.14.1",
6565
"typescript": "^4.0.3",
6666
"yup": "^0.28.1"

packages/formik/src/Field.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ export interface FieldConfig<V = any> {
5252
*/
5353
validate?: FieldValidator;
5454

55+
/**
56+
* Used for 'select' and related input types.
57+
*/
58+
multiple?: boolean;
59+
5560
/**
5661
* Field name
5762
*/

packages/formik/src/FieldArray.tsx

+7-13
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ class FieldArrayInner<Values = {}> extends React.Component<
178178

179179
formik: { setFormikState },
180180
} = this.props;
181+
181182
setFormikState((prevState: FormikState<any>) => {
182183
let updateErrors = createAlterationHandler(alterErrors, fn);
183184
let updateTouched = createAlterationHandler(alterTouched, fn);
@@ -268,26 +269,19 @@ class FieldArrayInner<Values = {}> extends React.Component<
268269
this.updateArrayField(
269270
(array: any[]) => {
270271
const arr = array ? [value, ...array] : [value];
271-
if (length < 0) {
272-
length = arr.length;
273-
}
272+
273+
length = arr.length;
274+
274275
return arr;
275276
},
276277
(array: any[]) => {
277-
const arr = array ? [null, ...array] : [null];
278-
if (length < 0) {
279-
length = arr.length;
280-
}
281-
return arr;
278+
return array ? [null, ...array] : [null];
282279
},
283280
(array: any[]) => {
284-
const arr = array ? [null, ...array] : [null];
285-
if (length < 0) {
286-
length = arr.length;
287-
}
288-
return arr;
281+
return array ? [null, ...array] : [null];
289282
}
290283
);
284+
291285
return length;
292286
};
293287

packages/formik/src/Formik.tsx

+34-21
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,34 @@
1-
import * as React from 'react';
2-
import isEqual from 'react-fast-compare';
31
import deepmerge from 'deepmerge';
42
import isPlainObject from 'lodash/isPlainObject';
3+
import * as React from 'react';
4+
import isEqual from 'react-fast-compare';
5+
import invariant from 'tiny-warning';
6+
import { FieldConfig } from './Field';
7+
import { FormikProvider } from './FormikContext';
58
import {
9+
FieldHelperProps,
10+
FieldInputProps,
11+
FieldMetaProps,
612
FormikConfig,
713
FormikErrors,
14+
FormikHandlers,
15+
FormikHelpers,
16+
FormikProps,
817
FormikState,
918
FormikTouched,
1019
FormikValues,
11-
FormikProps,
12-
FieldMetaProps,
13-
FieldHelperProps,
14-
FieldInputProps,
15-
FormikHelpers,
16-
FormikHandlers,
1720
} from './types';
1821
import {
22+
getActiveElement,
23+
getIn,
24+
isEmptyChildren,
1925
isFunction,
26+
isObject,
27+
isPromise,
2028
isString,
2129
setIn,
22-
isEmptyChildren,
23-
isPromise,
2430
setNestedObjectValues,
25-
getActiveElement,
26-
getIn,
27-
isObject,
2831
} from './utils';
29-
import { FormikProvider } from './FormikContext';
30-
import invariant from 'tiny-warning';
3132

3233
type FormikMessage<Values> =
3334
| { type: 'SUBMIT_ATTEMPT' }
@@ -170,9 +171,8 @@ export function useFormik<Values extends FormikValues = FormikValues>({
170171
};
171172
}, []);
172173

173-
const [state, dispatch] = React.useReducer<
174-
React.Reducer<FormikState<Values>, FormikMessage<Values>>
175-
>(formikReducer, {
174+
const [, setIteration] = React.useState(0);
175+
const stateRef = React.useRef<FormikState<Values>>({
176176
values: props.initialValues,
177177
errors: props.initialErrors || emptyErrors,
178178
touched: props.initialTouched || emptyTouched,
@@ -182,6 +182,17 @@ export function useFormik<Values extends FormikValues = FormikValues>({
182182
submitCount: 0,
183183
});
184184

185+
const state = stateRef.current;
186+
187+
const dispatch = React.useCallback((action: FormikMessage<Values>) => {
188+
const prev = stateRef.current;
189+
190+
stateRef.current = formikReducer(prev, action);
191+
192+
// force rerender
193+
if (prev !== stateRef.current) setIteration(x => x + 1);
194+
}, []);
195+
185196
const runValidateHandler = React.useCallback(
186197
(values: Values, field?: string): Promise<FormikErrors<Values>> => {
187198
return new Promise((resolve, reject) => {
@@ -888,9 +899,11 @@ export function useFormik<Values extends FormikValues = FormikValues>({
888899
);
889900

890901
const getFieldProps = React.useCallback(
891-
(nameOrOptions): FieldInputProps<any> => {
902+
(nameOrOptions: string | FieldConfig<any>): FieldInputProps<any> => {
892903
const isAnObject = isObject(nameOrOptions);
893-
const name = isAnObject ? nameOrOptions.name : nameOrOptions;
904+
const name = isAnObject
905+
? (nameOrOptions as FieldConfig<any>).name
906+
: nameOrOptions;
894907
const valueState = getIn(state.values, name);
895908

896909
const field: FieldInputProps<any> = {
@@ -905,7 +918,7 @@ export function useFormik<Values extends FormikValues = FormikValues>({
905918
value: valueProp, // value is special for checkboxes
906919
as: is,
907920
multiple,
908-
} = nameOrOptions;
921+
} = nameOrOptions as FieldConfig<any>;
909922

910923
if (type === 'checkbox') {
911924
if (valueProp === undefined) {

packages/formik/src/connect.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import invariant from 'tiny-warning';
1212
export function connect<OuterProps, Values = {}>(
1313
Comp: React.ComponentType<OuterProps & { formik: FormikContextType<Values> }>
1414
) {
15-
const C: React.FC<OuterProps> = (props: OuterProps) => (
15+
const C: React.FC<OuterProps> = props => (
1616
<FormikConsumer>
1717
{formik => {
1818
invariant(
@@ -23,6 +23,7 @@ export function connect<OuterProps, Values = {}>(
2323
}}
2424
</FormikConsumer>
2525
);
26+
2627
const componentDisplayName =
2728
Comp.displayName ||
2829
Comp.name ||
@@ -32,7 +33,7 @@ export function connect<OuterProps, Values = {}>(
3233
// Assign Comp to C.WrappedComponent so we can access the inner component in tests
3334
// For example, <Field.WrappedComponent /> gets us <FieldInner/>
3435
(C as React.FC<OuterProps> & {
35-
WrappedComponent: React.ReactNode;
36+
WrappedComponent: typeof Comp;
3637
}).WrappedComponent = Comp;
3738

3839
C.displayName = `FormikConnect(${componentDisplayName})`;
@@ -42,5 +43,5 @@ export function connect<OuterProps, Values = {}>(
4243
Comp as React.ComponentClass<
4344
OuterProps & { formik: FormikContextType<Values> }
4445
> // cast type to ComponentClass (even if SFC)
45-
) as React.ComponentType<OuterProps>;
46+
);
4647
}

packages/formik/src/types.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as React from 'react';
2+
import { FieldConfig } from './Field';
23
/**
34
* Values of fields in the form
45
*/
@@ -149,7 +150,9 @@ export interface FormikHandlers {
149150
: (e: string | React.ChangeEvent<any>) => void;
150151
};
151152

152-
getFieldProps: <Value = any>(props: any) => FieldInputProps<Value>;
153+
getFieldProps: <Value = any>(
154+
props: string | FieldConfig<Value>
155+
) => FieldInputProps<Value>;
153156
getFieldMeta: <Value>(name: string) => FieldMetaProps<Value>;
154157
getFieldHelpers: <Value = any>(name: string) => FieldHelperProps<Value>;
155158
}
@@ -177,7 +180,7 @@ export interface FormikConfig<Values> extends FormikSharedConfig {
177180
/**
178181
* Form component to render
179182
*/
180-
component?: React.ComponentType<FormikProps<Values>> | React.ReactNode;
183+
component?: React.ComponentType<FormikProps<Values>>;
181184

182185
/**
183186
* Render prop (works like React router's <Route render={props =>} />)
@@ -262,7 +265,7 @@ export interface SharedRenderProps<T> {
262265
/**
263266
* Field component to render. Can either be a string like 'select' or a component.
264267
*/
265-
component?: string | React.ComponentType<T | void>;
268+
component?: keyof JSX.IntrinsicElements | React.ComponentType<T | void>;
266269

267270
/**
268271
* Render prop (works like React router's <Route render={props =>} />)

packages/formik/src/withFormik.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export interface WithFormikConfig<
8181

8282
export type CompositeComponent<P> =
8383
| React.ComponentClass<P>
84-
| React.StatelessComponent<P>;
84+
| React.FunctionComponent<P>;
8585

8686
export interface ComponentDecorator<TOwnProps, TMergedProps> {
8787
(component: CompositeComponent<TMergedProps>): React.ComponentType<TOwnProps>;

packages/formik/test/ErrorMessage.test.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ fdescribe('<ErrorMessage />', () => {
4545

4646
await act(async () => {
4747
await actualFProps.setFieldTouched('email');
48+
await actualFProps.setFieldError('email', message);
4849
});
4950

5051
// Renders after being visited with an error.

packages/formik/test/FieldArray.test.tsx

+11-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { act, fireEvent, render, screen } from '@testing-library/react';
22
import * as React from 'react';
33
import * as Yup from 'yup';
44

5-
import { FieldArray, Formik, isFunction } from '../src';
5+
import { FieldArray, FieldArrayRenderProps, Formik, isFunction } from '../src';
66

77
const noop = () => {};
88

@@ -80,7 +80,7 @@ describe('<FieldArray />', () => {
8080
describe('props.push()', () => {
8181
it('should add a value to the end of the field array', () => {
8282
let formikBag: any;
83-
let arrayHelpers: any;
83+
let arrayHelpers: FieldArrayRenderProps;
8484
render(
8585
<TestForm>
8686
{(props: any) => {
@@ -154,7 +154,7 @@ describe('<FieldArray />', () => {
154154
it('should push clone not actual reference', () => {
155155
let personTemplate = { firstName: '', lastName: '' };
156156
let formikBag: any;
157-
let arrayHelpers: any;
157+
let arrayHelpers: FieldArrayRenderProps;
158158
render(
159159
<TestForm initialValues={{ people: [] }}>
160160
{(props: any) => {
@@ -187,7 +187,7 @@ describe('<FieldArray />', () => {
187187
describe('props.pop()', () => {
188188
it('should remove and return the last value from the field array', () => {
189189
let formikBag: any;
190-
let arrayHelpers: any;
190+
let arrayHelpers: FieldArrayRenderProps;
191191
render(
192192
<TestForm>
193193
{(props: any) => {
@@ -217,7 +217,7 @@ describe('<FieldArray />', () => {
217217
describe('props.swap()', () => {
218218
it('should swap two values in field array', () => {
219219
let formikBag: any;
220-
let arrayHelpers: any;
220+
let arrayHelpers: FieldArrayRenderProps;
221221
render(
222222
<TestForm>
223223
{(props: any) => {
@@ -246,7 +246,7 @@ describe('<FieldArray />', () => {
246246
describe('props.insert()', () => {
247247
it('should insert a value at given index of field array', () => {
248248
let formikBag: any;
249-
let arrayHelpers: any;
249+
let arrayHelpers: FieldArrayRenderProps;
250250
render(
251251
<TestForm>
252252
{(props: any) => {
@@ -275,7 +275,7 @@ describe('<FieldArray />', () => {
275275
describe('props.replace()', () => {
276276
it('should replace a value at given index of field array', () => {
277277
let formikBag: any;
278-
let arrayHelpers: any;
278+
let arrayHelpers: FieldArrayRenderProps;
279279
render(
280280
<TestForm>
281281
{(props: any) => {
@@ -304,7 +304,7 @@ describe('<FieldArray />', () => {
304304
describe('props.unshift()', () => {
305305
it('should add a value to start of field array and return its length', () => {
306306
let formikBag: any;
307-
let arrayHelpers: any;
307+
let arrayHelpers: FieldArrayRenderProps;
308308
render(
309309
<TestForm>
310310
{(props: any) => {
@@ -334,7 +334,7 @@ describe('<FieldArray />', () => {
334334

335335
describe('props.remove()', () => {
336336
let formikBag: any;
337-
let arrayHelpers: any;
337+
let arrayHelpers: FieldArrayRenderProps;
338338

339339
beforeEach(() => {
340340
render(
@@ -396,7 +396,7 @@ describe('<FieldArray />', () => {
396396
describe('given array-like object representing errors', () => {
397397
it('should run arrayHelpers successfully', async () => {
398398
let formikBag: any;
399-
let arrayHelpers: any;
399+
let arrayHelpers: FieldArrayRenderProps;
400400
render(
401401
<TestForm>
402402
{(props: any) => {
@@ -440,7 +440,7 @@ describe('<FieldArray />', () => {
440440
});
441441

442442
let formikBag: any;
443-
let arrayHelpers: any;
443+
let arrayHelpers: FieldArrayRenderProps;
444444

445445
beforeEach(() => {
446446
render(

0 commit comments

Comments
 (0)