Formik: Add Flow definitions to flow-typed repository

Created on 22 Jul 2017  路  24Comments  路  Source: formium/formik

Even though Formik is using Typescript, it would still be great to have Flow type definitions added to the Flow-typed repository. By just adding a description of the Formik interface using Flow in this repository, Formik functions could be type checked in Flow projects also.

Enhancement help wanted

Most helpful comment

Guys I did good (I think) flow library definitions for formik

Here is it:

declare module 'formik' {
  declare type UnaryFn<A, R> = (a: A) => R;
  declare type Component<A> = React$ComponentType<A>;

  declare export type HOC<Base, Enhanced> = UnaryFn<
    Component<Base>,
    Component<Enhanced>,
  >;

  declare export function Formik<Enhanced, Values>(
    // Formik configuration options
    options: {
      displayName?: string,
      mapPropsToValues?: (props: Enhanced) => Values,
      handleSubmit?: (
        values: Values,
        formikBag: {
          props: Enhanced,
          setValues: (values: Values) => void,
          setFieldValue: <K: $Keys<Values>>(field: K, value: any) => void,
          setErrors: (errors: { [key: $Keys<Values>]: string }) => void,
          setFieldError: <K: $Keys<Values>>(field: K, message: string) => void,
          setTouched: (touched: { [key: $Keys<Values>]: boolean }) => void,
          setFieldTouched: <K: $Keys<Values>>(
            field: K,
            isTouched?: boolean,
          ) => void,
          setSubmitting: (isSubmitting: boolean) => void,
          setStatus: (status: any) => void,
          resetForm: (nextProps?: Enhanced) => void,
          submitForm: () => void,
        },
      ) => void,
      validate?: (
        values: Values,
        props: Enhanced,
      ) =>
        | void
        | $ObjMap<Values, <K>(k: K) => string>
        | Promise<void | $ObjMap<Values, <K>(k: K) => string>>,
      validationSchema?: ((props: Enhanced) => any) | any,
      validateOnChange?: boolean,
      validateOnBlur?: boolean,
      isInitialValid?: boolean | ((props: Enhanced) => boolean | void),
    },
  ): HOC<
    {
      ...$Exact<Enhanced>,

      // FormikComputedProps
      dirty: boolean,
      isValid: boolean,
      // Formik state and state helpers
      values: Values,
      setValues: (values: Values) => void,
      setFieldValue: <K: $Keys<Values>>(field: K, value: any) => void,

      errors: $ObjMap<Values, <K>(k: K) => ?string>,
      setErrors: (errors: { [key: $Keys<Values>]: string }) => void,
      setFieldError: <K: $Keys<Values>>(field: K, message: string) => void,

      touched: $ObjMap<Values, <K>(k: K) => boolean | void>,
      setTouched: (touched: { [key: $Keys<Values>]: boolean }) => void,
      setFieldTouched: <K: $Keys<Values>>(
        field: K,
        isTouched?: boolean,
      ) => void,

      isSubmitting: boolean,
      setSubmitting: (isSubmitting: boolean) => void,

      status?: any,
      setStatus: (status: any) => void,

      resetForm: (nextProps?: Enhanced) => void,
      submitForm: () => void,

      // FormikHandlers

      handleSubmit: (e: SyntheticEvent<EventTarget>) => void,
      handleChange: (e: SyntheticEvent<EventTarget>) => void,
      handleChangeValue: (name: string, value: any) => void,
      handleBlur: (e: SyntheticEvent<EventTarget>) => void,
      handleReset: () => void,
    },
    Enhanced,
  >;
}

It works well in combination with recompose HOCs, and uses the same usage idea
as described here and at medium article

So all you need to use it is to import HOC type and to declare formik enhancer with it

/* @flow */

import * as React from 'react';
import { Formik } from 'formik';
import type { HOC } from 'formik';

const myForm = ({
  values,
  errors,
  handleChange,
  setFieldError,
  handleSubmit,
}) => (
  <form onSubmit={handleSubmit}>
    <input id="name" value={values.name} onChange={handleChange} />
    <input id="age" value={values.age} onChange={handleChange} />
    <div onClick={() => setFieldError('age', 'bad age')}>
      {errors.name} {errors.age}
    </div>
  </form>
);

// The only type you need to define
type MyProps = {
  someObj: {
    name: string,
    age: number,
  },
};

const formikEnhancer: HOC<*, MyProps> = Formik({
  mapPropsToValues: ({ someObj }) => ({
    name: someObj.name,
    age: someObj.age,
    someStrangeValue: {
      id: someObj.age,
    },
  }),
  handleSubmit: (values, { props, setSubmitting, setFieldError }) => {
    setSubmitting(true);
    setFieldError('age', 'bad bad');
  },
});

export default formikEnhancer(myForm);

Type inference work well see image

image

Errors detected well

image

Feel free to add this into flowtyped and contact me if you have any questions

All 24 comments

Automatic conversion could help with take off https://github.com/joarwilk/flowgen

FWIW, I generated a definition with flowgen and manually tweaked it to get things sort-of working here https://gist.github.com/jdelStrother/8227ce99250e366cd9963686f515278a
It successfully checks the call to Formik() - eg something like this would fail:

export default Formik({
})(SignupForm)

because handleSubmit wasn't defined.

However, it doesn't typecheck the component being wrapped - I can't figure out how to express something like this:

type SignupProps = {bannerMessage: string} & FormikProps
const SignupForm = ({bannerMessage, values, errors, etc}: SignupProps) => <div />
export default Formik({...})(SignupForm)

such that it would warn about missing bannerMessage in props.

HOCs with Flow are kinda issue right now. Will try to dig into this. Awesome work :)

Making some progress on this field, stay tuned 馃帀

Having a problem with typing main function.

My Typescript knowledge is very basic, what I understand this is possible

function Formik<Props, Values>(config: any) {
  return b;
}

// you can specify those generics at call site
interface P {
  email: string,
  otherProp: number
}

interface V {
  email: string
} 

const enhancer = Formik<P, V>({/*...*/})

Unfortunately, you cannot specify generic at call site for Flow. Instead, they could be extracted from params.

function Formik<Props, Values>(param1: Props, param2: Values) {
  return b;
}

interface P {
  email: string,
  otherProp: number
}

interface V {
  email: string
} 

const enhancer = Formik(props: P, values: V)

Any ideas how to solve this?

Stopping work on flow typings until new API is released as stable

@jdelStrother do you have an example of using this flow-typed in a code?

@sibelius I'm not sure what you're asking - there's this example, does that help?

type SignupProps = {bannerMessage: string} & FormikProps
const SignupForm = ({bannerMessage, values, errors, etc}: SignupProps) => <div />
export default Formik({...})(SignupForm)

there is no FormikProps in your flow-typed example

Guys I did good (I think) flow library definitions for formik

Here is it:

declare module 'formik' {
  declare type UnaryFn<A, R> = (a: A) => R;
  declare type Component<A> = React$ComponentType<A>;

  declare export type HOC<Base, Enhanced> = UnaryFn<
    Component<Base>,
    Component<Enhanced>,
  >;

  declare export function Formik<Enhanced, Values>(
    // Formik configuration options
    options: {
      displayName?: string,
      mapPropsToValues?: (props: Enhanced) => Values,
      handleSubmit?: (
        values: Values,
        formikBag: {
          props: Enhanced,
          setValues: (values: Values) => void,
          setFieldValue: <K: $Keys<Values>>(field: K, value: any) => void,
          setErrors: (errors: { [key: $Keys<Values>]: string }) => void,
          setFieldError: <K: $Keys<Values>>(field: K, message: string) => void,
          setTouched: (touched: { [key: $Keys<Values>]: boolean }) => void,
          setFieldTouched: <K: $Keys<Values>>(
            field: K,
            isTouched?: boolean,
          ) => void,
          setSubmitting: (isSubmitting: boolean) => void,
          setStatus: (status: any) => void,
          resetForm: (nextProps?: Enhanced) => void,
          submitForm: () => void,
        },
      ) => void,
      validate?: (
        values: Values,
        props: Enhanced,
      ) =>
        | void
        | $ObjMap<Values, <K>(k: K) => string>
        | Promise<void | $ObjMap<Values, <K>(k: K) => string>>,
      validationSchema?: ((props: Enhanced) => any) | any,
      validateOnChange?: boolean,
      validateOnBlur?: boolean,
      isInitialValid?: boolean | ((props: Enhanced) => boolean | void),
    },
  ): HOC<
    {
      ...$Exact<Enhanced>,

      // FormikComputedProps
      dirty: boolean,
      isValid: boolean,
      // Formik state and state helpers
      values: Values,
      setValues: (values: Values) => void,
      setFieldValue: <K: $Keys<Values>>(field: K, value: any) => void,

      errors: $ObjMap<Values, <K>(k: K) => ?string>,
      setErrors: (errors: { [key: $Keys<Values>]: string }) => void,
      setFieldError: <K: $Keys<Values>>(field: K, message: string) => void,

      touched: $ObjMap<Values, <K>(k: K) => boolean | void>,
      setTouched: (touched: { [key: $Keys<Values>]: boolean }) => void,
      setFieldTouched: <K: $Keys<Values>>(
        field: K,
        isTouched?: boolean,
      ) => void,

      isSubmitting: boolean,
      setSubmitting: (isSubmitting: boolean) => void,

      status?: any,
      setStatus: (status: any) => void,

      resetForm: (nextProps?: Enhanced) => void,
      submitForm: () => void,

      // FormikHandlers

      handleSubmit: (e: SyntheticEvent<EventTarget>) => void,
      handleChange: (e: SyntheticEvent<EventTarget>) => void,
      handleChangeValue: (name: string, value: any) => void,
      handleBlur: (e: SyntheticEvent<EventTarget>) => void,
      handleReset: () => void,
    },
    Enhanced,
  >;
}

It works well in combination with recompose HOCs, and uses the same usage idea
as described here and at medium article

So all you need to use it is to import HOC type and to declare formik enhancer with it

/* @flow */

import * as React from 'react';
import { Formik } from 'formik';
import type { HOC } from 'formik';

const myForm = ({
  values,
  errors,
  handleChange,
  setFieldError,
  handleSubmit,
}) => (
  <form onSubmit={handleSubmit}>
    <input id="name" value={values.name} onChange={handleChange} />
    <input id="age" value={values.age} onChange={handleChange} />
    <div onClick={() => setFieldError('age', 'bad age')}>
      {errors.name} {errors.age}
    </div>
  </form>
);

// The only type you need to define
type MyProps = {
  someObj: {
    name: string,
    age: number,
  },
};

const formikEnhancer: HOC<*, MyProps> = Formik({
  mapPropsToValues: ({ someObj }) => ({
    name: someObj.name,
    age: someObj.age,
    someStrangeValue: {
      id: someObj.age,
    },
  }),
  handleSubmit: (values, { props, setSubmitting, setFieldError }) => {
    setSubmitting(true);
    setFieldError('age', 'bad bad');
  },
});

export default formikEnhancer(myForm);

Type inference work well see image

image

Errors detected well

image

Feel free to add this into flowtyped and contact me if you have any questions

@istarkov hey, you might be interested in following this one 馃槈

How about exporting types with "namespace", to prevent name collisions.
E.g. HOC => FormikHOC

@istarkov looks good

are you gonna send a PR do flow-typed repo?

I think FormikHOC would be a good name

Sorry guys have no time for PR, please make it

There will be no collision with names, u can change name on import

I also have version with typed status, but found it useless in current realisation (no ability to merge with prev) so decided not to provide it here

There will be no collision with names, u can change name on import

While that's true, would be explicit name better?
Can take care of PR if you're not against

@Andreyco thank you it will be great!

@Andreyco any updates?

@aaronjensen could you take a look to this issue #696 ? Thanks!

@DTupalov sorry, I'm no longer using Formik nor Flow. Typing HoCs is way too challenging in flow (or at least it used to be, I'm not too interested in finding out if it's easier now...) Sorry, but good luck!

@aaronjensen sadly :(

@istarkov maybe you can help with advice in #696 issue?

I also switched to non hoc version of formik and use local defs defined here
https://github.com/jaredpalmer/formik/pull/591

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pmonty picture pmonty  路  3Comments

jaredpalmer picture jaredpalmer  路  3Comments

ancashoria picture ancashoria  路  3Comments

outaTiME picture outaTiME  路  3Comments

najisawas picture najisawas  路  3Comments