or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

context-integration.mdcore-components.mderror-handling.mdfield-arrays.mdform-state-management.mdindex.md
tile.json

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