CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-formik

Build forms in React, without the tears

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

context-integration.mddocs/

Context & Integration

React Context providers and higher-order components for advanced integration patterns and legacy component support.

Capabilities

Formik Context

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>
);

withFormik Higher-Order Component

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);

connect Higher-Order Component

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>
);

Advanced Integration Patterns

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>
  );
};

docs

context-integration.md

core-components.md

error-handling.md

field-arrays.md

form-state-management.md

index.md

tile.json