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

refactor: react 18 internal upgrade #3817

Merged
merged 11 commits into from
Jun 14, 2023
5 changes: 5 additions & 0 deletions .changeset/twenty-lions-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'formik': patch
---

Updated internal types to support React 18.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
{
"name": "formik-project",
"private": true,
"resolutions": {
"shelljs": "0.8.5",
"typescript": "^4.0.0"
},
"devDependencies": {
"@changesets/changelog-github": "^0.2.7",
"@changesets/cli": "^2.10.3",
"@playwright/test": "^1.34.3",
"@types/jest": "^26.0.14",
"@types/jest": "^25.0.0",
"husky": "^4.3.0",
"lint-staged": "^10.4.0",
"prettier": "^2.1.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/formik-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
},
"devDependencies": {
"@react-native-community/eslint-config": "^0.0.5",
"@types/react": "^16.9.55",
"@types/react": "^18.2.7",
"@types/react-native": "^0.63.32",
"react": "^18.2.0",
"react-native": "https://github.com/expo/react-native/archive/sdk-33.0.0.tar.gz",
Expand Down
12 changes: 6 additions & 6 deletions packages/formik/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,19 @@
"lodash-es": "^4.17.21",
"react-fast-compare": "^2.0.1",
"tiny-warning": "^1.0.2",
"tslib": "^1.10.0"
"tslib": "^2.0.0"
},
"devDependencies": {
"@testing-library/react": "^11.1.0",
"@testing-library/react": "^14.0.0",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/lodash": "^4.14.119",
"@types/react": "^16.9.55",
"@types/react-dom": "^16.9.9",
"@types/react": "^18.2.7",
"@types/react-dom": "^18.2.4",
"@types/warning": "^3.0.0",
"@types/yup": "^0.24.9",
"just-debounce-it": "^1.1.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tsdx": "^0.14.1",
"typescript": "^4.0.3",
"yup": "^0.28.1"
Expand Down
5 changes: 5 additions & 0 deletions packages/formik/src/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ export interface FieldConfig<V = any> {
*/
validate?: FieldValidator;

/**
* Used for 'select' and related input types.
*/
multiple?: boolean;

/**
* Field name
*/
Expand Down
20 changes: 7 additions & 13 deletions packages/formik/src/FieldArray.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ class FieldArrayInner<Values = {}> extends React.Component<

formik: { setFormikState },
} = this.props;

setFormikState((prevState: FormikState<any>) => {
let updateErrors = createAlterationHandler(alterErrors, fn);
let updateTouched = createAlterationHandler(alterTouched, fn);
Expand Down Expand Up @@ -268,26 +269,19 @@ class FieldArrayInner<Values = {}> extends React.Component<
this.updateArrayField(
(array: any[]) => {
const arr = array ? [value, ...array] : [value];
if (length < 0) {
length = arr.length;
}

length = arr.length;

return arr;
},
(array: any[]) => {
const arr = array ? [null, ...array] : [null];
if (length < 0) {
length = arr.length;
}
return arr;
return array ? [null, ...array] : [null];
},
(array: any[]) => {
const arr = array ? [null, ...array] : [null];
if (length < 0) {
length = arr.length;
}
return arr;
return array ? [null, ...array] : [null];
}
);

return length;
};

Expand Down
55 changes: 34 additions & 21 deletions packages/formik/src/Formik.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
import * as React from 'react';
import isEqual from 'react-fast-compare';
import deepmerge from 'deepmerge';
import isPlainObject from 'lodash/isPlainObject';
import * as React from 'react';
import isEqual from 'react-fast-compare';
import invariant from 'tiny-warning';
import { FieldConfig } from './Field';
import { FormikProvider } from './FormikContext';
import {
FieldHelperProps,
FieldInputProps,
FieldMetaProps,
FormikConfig,
FormikErrors,
FormikHandlers,
FormikHelpers,
FormikProps,
FormikState,
FormikTouched,
FormikValues,
FormikProps,
FieldMetaProps,
FieldHelperProps,
FieldInputProps,
FormikHelpers,
FormikHandlers,
} from './types';
import {
getActiveElement,
getIn,
isEmptyChildren,
isFunction,
isObject,
isPromise,
isString,
setIn,
isEmptyChildren,
isPromise,
setNestedObjectValues,
getActiveElement,
getIn,
isObject,
} from './utils';
import { FormikProvider } from './FormikContext';
import invariant from 'tiny-warning';

type FormikMessage<Values> =
| { type: 'SUBMIT_ATTEMPT' }
Expand Down Expand Up @@ -170,9 +171,8 @@ export function useFormik<Values extends FormikValues = FormikValues>({
};
}, []);

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

const state = stateRef.current;

const dispatch = React.useCallback((action: FormikMessage<Values>) => {
const prev = stateRef.current;

stateRef.current = formikReducer(prev, action);

// force rerender
if (prev !== stateRef.current) setIteration(x => x + 1);
}, []);

const runValidateHandler = React.useCallback(
(values: Values, field?: string): Promise<FormikErrors<Values>> => {
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -888,9 +899,11 @@ export function useFormik<Values extends FormikValues = FormikValues>({
);

const getFieldProps = React.useCallback(
(nameOrOptions): FieldInputProps<any> => {
(nameOrOptions: string | FieldConfig<any>): FieldInputProps<any> => {
const isAnObject = isObject(nameOrOptions);
const name = isAnObject ? nameOrOptions.name : nameOrOptions;
const name = isAnObject
? (nameOrOptions as FieldConfig<any>).name
: nameOrOptions;
const valueState = getIn(state.values, name);

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

if (type === 'checkbox') {
if (valueProp === undefined) {
Expand Down
7 changes: 4 additions & 3 deletions packages/formik/src/connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import invariant from 'tiny-warning';
export function connect<OuterProps, Values = {}>(
Comp: React.ComponentType<OuterProps & { formik: FormikContextType<Values> }>
) {
const C: React.FC<OuterProps> = (props: OuterProps) => (
const C: React.FC<OuterProps> = props => (
<FormikConsumer>
{formik => {
invariant(
Expand All @@ -23,6 +23,7 @@ export function connect<OuterProps, Values = {}>(
}}
</FormikConsumer>
);

const componentDisplayName =
Comp.displayName ||
Comp.name ||
Expand All @@ -32,7 +33,7 @@ export function connect<OuterProps, Values = {}>(
// Assign Comp to C.WrappedComponent so we can access the inner component in tests
// For example, <Field.WrappedComponent /> gets us <FieldInner/>
(C as React.FC<OuterProps> & {
WrappedComponent: React.ReactNode;
WrappedComponent: typeof Comp;
}).WrappedComponent = Comp;

C.displayName = `FormikConnect(${componentDisplayName})`;
Expand All @@ -42,5 +43,5 @@ export function connect<OuterProps, Values = {}>(
Comp as React.ComponentClass<
OuterProps & { formik: FormikContextType<Values> }
> // cast type to ComponentClass (even if SFC)
) as React.ComponentType<OuterProps>;
);
}
9 changes: 6 additions & 3 deletions packages/formik/src/types.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import { FieldConfig } from './Field';
/**
* Values of fields in the form
*/
Expand Down Expand Up @@ -149,7 +150,9 @@ export interface FormikHandlers {
: (e: string | React.ChangeEvent<any>) => void;
};

getFieldProps: <Value = any>(props: any) => FieldInputProps<Value>;
getFieldProps: <Value = any>(
props: string | FieldConfig<Value>
) => FieldInputProps<Value>;
getFieldMeta: <Value>(name: string) => FieldMetaProps<Value>;
getFieldHelpers: <Value = any>(name: string) => FieldHelperProps<Value>;
}
Expand Down Expand Up @@ -177,7 +180,7 @@ export interface FormikConfig<Values> extends FormikSharedConfig {
/**
* Form component to render
*/
component?: React.ComponentType<FormikProps<Values>> | React.ReactNode;
component?: React.ComponentType<FormikProps<Values>>;

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

/**
* Render prop (works like React router's <Route render={props =>} />)
Expand Down
2 changes: 1 addition & 1 deletion packages/formik/src/withFormik.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export interface WithFormikConfig<

export type CompositeComponent<P> =
| React.ComponentClass<P>
| React.StatelessComponent<P>;
| React.FunctionComponent<P>;

export interface ComponentDecorator<TOwnProps, TMergedProps> {
(component: CompositeComponent<TMergedProps>): React.ComponentType<TOwnProps>;
Expand Down
1 change: 1 addition & 0 deletions packages/formik/test/ErrorMessage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ fdescribe('<ErrorMessage />', () => {

await act(async () => {
await actualFProps.setFieldTouched('email');
await actualFProps.setFieldError('email', message);
});

// Renders after being visited with an error.
Expand Down
22 changes: 11 additions & 11 deletions packages/formik/test/FieldArray.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { act, fireEvent, render, screen } from '@testing-library/react';
import * as React from 'react';
import * as Yup from 'yup';

import { FieldArray, Formik, isFunction } from '../src';
import { FieldArray, FieldArrayRenderProps, Formik, isFunction } from '../src';

const noop = () => {};

Expand Down Expand Up @@ -80,7 +80,7 @@ describe('<FieldArray />', () => {
describe('props.push()', () => {
it('should add a value to the end of the field array', () => {
let formikBag: any;
let arrayHelpers: any;
let arrayHelpers: FieldArrayRenderProps;
render(
<TestForm>
{(props: any) => {
Expand Down Expand Up @@ -154,7 +154,7 @@ describe('<FieldArray />', () => {
it('should push clone not actual reference', () => {
let personTemplate = { firstName: '', lastName: '' };
let formikBag: any;
let arrayHelpers: any;
let arrayHelpers: FieldArrayRenderProps;
render(
<TestForm initialValues={{ people: [] }}>
{(props: any) => {
Expand Down Expand Up @@ -187,7 +187,7 @@ describe('<FieldArray />', () => {
describe('props.pop()', () => {
it('should remove and return the last value from the field array', () => {
let formikBag: any;
let arrayHelpers: any;
let arrayHelpers: FieldArrayRenderProps;
render(
<TestForm>
{(props: any) => {
Expand Down Expand Up @@ -217,7 +217,7 @@ describe('<FieldArray />', () => {
describe('props.swap()', () => {
it('should swap two values in field array', () => {
let formikBag: any;
let arrayHelpers: any;
let arrayHelpers: FieldArrayRenderProps;
render(
<TestForm>
{(props: any) => {
Expand Down Expand Up @@ -246,7 +246,7 @@ describe('<FieldArray />', () => {
describe('props.insert()', () => {
it('should insert a value at given index of field array', () => {
let formikBag: any;
let arrayHelpers: any;
let arrayHelpers: FieldArrayRenderProps;
render(
<TestForm>
{(props: any) => {
Expand Down Expand Up @@ -275,7 +275,7 @@ describe('<FieldArray />', () => {
describe('props.replace()', () => {
it('should replace a value at given index of field array', () => {
let formikBag: any;
let arrayHelpers: any;
let arrayHelpers: FieldArrayRenderProps;
render(
<TestForm>
{(props: any) => {
Expand Down Expand Up @@ -304,7 +304,7 @@ describe('<FieldArray />', () => {
describe('props.unshift()', () => {
it('should add a value to start of field array and return its length', () => {
let formikBag: any;
let arrayHelpers: any;
let arrayHelpers: FieldArrayRenderProps;
render(
<TestForm>
{(props: any) => {
Expand Down Expand Up @@ -334,7 +334,7 @@ describe('<FieldArray />', () => {

describe('props.remove()', () => {
let formikBag: any;
let arrayHelpers: any;
let arrayHelpers: FieldArrayRenderProps;

beforeEach(() => {
render(
Expand Down Expand Up @@ -396,7 +396,7 @@ describe('<FieldArray />', () => {
describe('given array-like object representing errors', () => {
it('should run arrayHelpers successfully', async () => {
let formikBag: any;
let arrayHelpers: any;
let arrayHelpers: FieldArrayRenderProps;
render(
<TestForm>
{(props: any) => {
Expand Down Expand Up @@ -440,7 +440,7 @@ describe('<FieldArray />', () => {
});

let formikBag: any;
let arrayHelpers: any;
let arrayHelpers: FieldArrayRenderProps;

beforeEach(() => {
render(
Expand Down
Loading