React Context providers and higher-order components for advanced integration patterns and legacy component support.
React Context system for accessing Formik state throughout component trees.
/**
* React Context for Formik state
*/
const FormikContext: React.Context<FormikContextType<any>>;
/**
* Context Provider component
*/
const FormikProvider: React.Provider<FormikContextType<any>>;
/**
* Context Consumer component
*/
const FormikConsumer: React.Consumer<FormikContextType<any>>;
/**
* Hook to access Formik context
* @returns Current Formik context value
* @throws Error if used outside Formik provider
*/
function useFormikContext<Values>(): FormikContextType<Values>;
interface FormikContextType<Values> extends FormikProps<Values> {
/** Custom validation function */
validate?: (values: Values) => void | object | Promise<FormikErrors<Values>>;
/** Yup validation schema */
validationSchema?: any;
}Usage Examples:
import { useFormikContext, Formik, Form, Field } from "formik";
// Custom component accessing Formik context
const FormStatus = () => {
const { isSubmitting, isValid, dirty, values } = useFormikContext();
return (
<div className="form-status">
<div>Form Valid: {isValid ? 'Yes' : 'No'}</div>
<div>Form Dirty: {dirty ? 'Yes' : 'No'}</div>
<div>Submitting: {isSubmitting ? 'Yes' : 'No'}</div>
<div>Values: {JSON.stringify(values)}</div>
</div>
);
};
// Submit button that accesses context
const SubmitButton = ({ children }) => {
const { isSubmitting, isValid } = useFormikContext();
return (
<button
type="submit"
disabled={isSubmitting || !isValid}
className={isSubmitting ? 'submitting' : ''}
>
{isSubmitting ? 'Submitting...' : children}
</button>
);
};
// Form using context components
const ContextForm = () => (
<Formik
initialValues={{ name: '', email: '' }}
validate={values => {
const errors = {};
if (!values.name) errors.name = 'Required';
if (!values.email) errors.email = 'Required';
return errors;
}}
onSubmit={values => console.log(values)}
>
<Form>
<Field name="name" placeholder="Name" />
<Field name="email" placeholder="Email" />
<FormStatus />
<SubmitButton>Submit Form</SubmitButton>
</Form>
</Formik>
);
// Deep nested component accessing context
const DeepNestedComponent = () => {
const formik = useFormikContext();
const handleClearForm = () => {
formik.resetForm();
};
const handleSetSpecificValue = () => {
formik.setFieldValue('email', 'example@test.com');
};
return (
<div>
<button type="button" onClick={handleClearForm}>
Clear Form
</button>
<button type="button" onClick={handleSetSpecificValue}>
Set Example Email
</button>
</div>
);
};
// Using FormikConsumer (legacy pattern)
const LegacyConsumerComponent = () => (
<FormikConsumer>
{formik => (
<div>
<div>Current values: {JSON.stringify(formik.values)}</div>
<button type="button" onClick={() => formik.resetForm()}>
Reset
</button>
</div>
)}
</FormikConsumer>
);Higher-order component for injecting Formik props into class components or legacy function components.
/**
* Higher-order component for injecting Formik props
* @param config - Configuration for Formik behavior
* @returns Component decorator function
*/
function withFormik<OuterProps, Values, Payload = Values>(
config: WithFormikConfig<OuterProps, Values, Payload>
): ComponentDecorator<OuterProps, OuterProps & InjectedFormikProps<OuterProps, Values>>;
interface WithFormikConfig<Props, Values, Payload = Values> {
/** Map props to initial values */
mapPropsToValues?: (props: Props) => Values;
/** Map props to validation schema */
mapPropsToValidationSchema?: (props: Props) => any;
/** Custom validation function */
validate?: (values: Values, props: Props) => void | object | Promise<FormikErrors<Values>>;
/** Form submission handler */
handleSubmit: (values: Values, formikBag: FormikBag<Props, Values>) => void | Promise<any>;
/** Display name for debugging */
displayName?: string;
/** Enable reinitialization when props change */
enableReinitialize?: boolean;
/** Whether initial form values are valid */
isInitialValid?: boolean | ((props: Props) => boolean);
/** Validate on change */
validateOnChange?: boolean;
/** Validate on blur */
validateOnBlur?: boolean;
/** Validate on mount */
validateOnMount?: boolean;
}
interface FormikBag<P, V> {
/** Component props */
props: P;
/** Set field value */
setFieldValue: (field: string, value: any, shouldValidate?: boolean) => Promise<void | FormikErrors<V>>;
/** Set field error */
setFieldError: (field: string, message: string | undefined) => void;
/** Set field touched */
setFieldTouched: (field: string, isTouched?: boolean, shouldValidate?: boolean) => Promise<void | FormikErrors<V>>;
/** Set form values */
setValues: (values: React.SetStateAction<V>, shouldValidate?: boolean) => Promise<void | FormikErrors<V>>;
/** Set form errors */
setErrors: (errors: FormikErrors<V>) => void;
/** Set form touched */
setTouched: (touched: FormikTouched<V>, shouldValidate?: boolean) => Promise<void | FormikErrors<V>>;
/** Set form status */
setStatus: (status?: any) => void;
/** Set submitting state */
setSubmitting: (isSubmitting: boolean) => void;
/** Reset form */
resetForm: (nextState?: Partial<FormikState<V>>) => void;
/** Submit form */
submitForm: () => Promise<void>;
/** Validate form */
validateForm: (values?: any) => Promise<FormikErrors<V>>;
/** Validate field */
validateField: (field: string) => Promise<void> | Promise<string | undefined>;
/** Set Formik state */
setFormikState: (f: FormikState<V> | ((prevState: FormikState<V>) => FormikState<V>), cb?: () => void) => void;
}
type InjectedFormikProps<Props, Values> = Props & FormikProps<Values>;
interface ComponentDecorator<TOwnProps, TMergedProps> {
(component: React.ComponentType<TMergedProps>): React.ComponentType<TOwnProps>;
}Usage Examples:
import { withFormik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup";
// Class component with withFormik
class InnerForm extends React.Component {
render() {
const {
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting
} = this.props;
return (
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
/>
{touched.email && errors.email && <div>{errors.email}</div>}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
);
}
}
// Enhanced component with Formik
const MyForm = withFormik({
mapPropsToValues: (props) => ({
email: props.email || '',
}),
validationSchema: Yup.object().shape({
email: Yup.string().email('Invalid email').required('Required'),
}),
handleSubmit: (values, { setSubmitting, props }) => {
console.log(values);
props.onSubmit(values);
setSubmitting(false);
},
displayName: 'MyForm',
})(InnerForm);
// Usage
const App = () => (
<MyForm
email="initial@example.com"
onSubmit={values => console.log('Submitted:', values)}
/>
);
// Function component with withFormik
const FunctionForm = ({ handleSubmit, values, handleChange, errors, touched }) => (
<form onSubmit={handleSubmit}>
<input
name="username"
value={values.username}
onChange={handleChange}
/>
{touched.username && errors.username && <div>{errors.username}</div>}
<button type="submit">Submit</button>
</form>
);
const EnhancedFunctionForm = withFormik({
mapPropsToValues: () => ({ username: '' }),
validate: (values) => {
const errors = {};
if (!values.username) {
errors.username = 'Username is required';
}
return errors;
},
handleSubmit: (values, { setSubmitting }) => {
setTimeout(() => {
console.log(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
},
})(FunctionForm);
// Complex configuration
const AdvancedForm = withFormik({
mapPropsToValues: (props) => ({
name: props.user?.name || '',
email: props.user?.email || '',
role: props.user?.role || 'user',
}),
mapPropsToValidationSchema: (props) => {
const baseSchema = {
name: Yup.string().required('Name is required'),
email: Yup.string().email('Invalid email').required('Email is required'),
};
if (props.isAdmin) {
baseSchema.role = Yup.string().oneOf(['user', 'admin'], 'Invalid role');
}
return Yup.object().shape(baseSchema);
},
handleSubmit: async (values, { props, setSubmitting, setStatus }) => {
try {
await props.updateUser(values);
setStatus({ type: 'success', message: 'User updated successfully' });
} catch (error) {
setStatus({ type: 'error', message: 'Failed to update user' });
} finally {
setSubmitting(false);
}
},
enableReinitialize: true,
validateOnMount: true,
displayName: 'AdvancedUserForm',
})(UserFormComponent);Internal HOC for connecting components to Formik context (primarily for internal use but available for advanced patterns).
/**
* Internal HOC for connecting components to Formik context
* @param Comp - Component to connect to Formik context
* @returns Connected component with Formik context access
*/
function connect<OuterProps, Values = {}>(
Comp: React.ComponentType<OuterProps & { formik: FormikProps<Values> }>
): React.ComponentType<OuterProps>;Usage Examples:
import { connect } from "formik";
// Custom component that needs Formik context
const CustomFieldComponent = ({ formik, name, ...props }) => {
const field = formik.getFieldProps(name);
const meta = formik.getFieldMeta(name);
return (
<div>
<input {...field} {...props} />
{meta.touched && meta.error && (
<div className="error">{meta.error}</div>
)}
</div>
);
};
// Connect component to Formik context
const ConnectedCustomField = connect(CustomFieldComponent);
// Usage in form
const FormWithConnectedField = () => (
<Formik
initialValues={{ customField: '' }}
validate={values => {
const errors = {};
if (!values.customField) {
errors.customField = 'This field is required';
}
return errors;
}}
onSubmit={values => console.log(values)}
>
<Form>
<ConnectedCustomField name="customField" placeholder="Custom Field" />
<button type="submit">Submit</button>
</Form>
</Formik>
);Complex integration scenarios and patterns.
Multi-Step Form with Context:
const StepContext = React.createContext();
const MultiStepForm = () => {
const [currentStep, setCurrentStep] = useState(0);
const steps = [
{ title: 'Personal Info', component: Step1 },
{ title: 'Contact Info', component: Step2 },
{ title: 'Review', component: Step3 },
];
return (
<Formik
initialValues={{
firstName: '',
lastName: '',
email: '',
phone: '',
}}
onSubmit={values => console.log('Final submission:', values)}
>
<StepContext.Provider value={{ currentStep, setCurrentStep, steps }}>
<Form>
<div className="step-indicator">
{steps.map((step, index) => (
<div
key={index}
className={index === currentStep ? 'active' : ''}
>
{step.title}
</div>
))}
</div>
{React.createElement(steps[currentStep].component)}
<NavigationButtons />
</Form>
</StepContext.Provider>
</Formik>
);
};
const NavigationButtons = () => {
const { currentStep, setCurrentStep, steps } = useContext(StepContext);
const { values, validateForm } = useFormikContext();
const goNext = async () => {
const errors = await validateForm();
if (Object.keys(errors).length === 0) {
setCurrentStep(prev => Math.min(prev + 1, steps.length - 1));
}
};
const goPrevious = () => {
setCurrentStep(prev => Math.max(prev - 1, 0));
};
return (
<div className="navigation">
{currentStep > 0 && (
<button type="button" onClick={goPrevious}>
Previous
</button>
)}
{currentStep < steps.length - 1 ? (
<button type="button" onClick={goNext}>
Next
</button>
) : (
<button type="submit">
Submit
</button>
)}
</div>
);
};Form with External State Management:
// Redux integration example
const ReduxFormikForm = ({ user, updateUser }) => {
const formik = useFormik({
initialValues: user,
enableReinitialize: true,
onSubmit: (values) => {
updateUser(values);
},
});
// Sync external state changes
useEffect(() => {
if (user !== formik.values) {
formik.setValues(user);
}
}, [user]);
return (
<FormikProvider value={formik}>
<form onSubmit={formik.handleSubmit}>
<Field name="name" />
<Field name="email" />
<ExternalStateDisplay />
<button type="submit">Update</button>
</form>
</FormikProvider>
);
};
const ExternalStateDisplay = () => {
const { values } = useFormikContext();
return (
<div className="external-state">
Current form state: {JSON.stringify(values)}
</div>
);
};