Custom Formik components with Typescript and Chakra UI
Published: 2021-08-27 11:17:55

Resources:
- [Formik tutorial](https://formik.org/docs/tutorial)
- [useField Formik hook with Typescript](https://stackoverflow.com/questions/61089182/how-to-properly-use-usefield-hook-from-formik-in-typescript)
- [Using Chakra UI with Formik](https://chakra-ui.com/docs/form/form-control#usage-with-form-libraries)
- [Prop 'id' did not match problem](https://github.com/chakra-ui/chakra-ui/issues/4328)

#### foreword
Hello everyone.  

I will be using `create next-app` throughout the post but everything in here is appliable to regular React (CRA) applications.

I expect you to be familiar with Chakra UI and Formik

Let's get started =)  
 <br/>
Let's start with creating a new NextJS app: ```yarn create next-app MyNextApp --typescript```

Open created folder in your IDE, then install Formik and Chakra UI 
```bash
yarn add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4 formik
```
Next we create a new folder `components` and a file named `CustomInput.tsx`

Inside that file create a new Functional component named `CustomInput`: 
```javascript
export const CustomInput: FC = ({ label, ...props }) => {
  const [field, meta] = useField(props);

  return (
    <FormControl isInvalid={meta.touched && !!meta.error}>
      <FormLabel>
        {label}
      </FormLabel>
      <Input {...field} />
    </FormControl>
  );
}
```
At this moment Typescript will yell at us saying it doesn't know what're the `label` and `props`, we need to explain to that silly what those things are. So we need an interface which can be written two ways: either by extending `JSX.IntrinsicElements['input']` interface or by intersecting `FieldHookConfig<string>` type: 

```javascript
// intersecting
interface ICustomFieldProps {
  label: string;
}

// FieldHookConfig accepts value type as an argument,
// it is 'string' for input elements
export const CustomInput: FC<FieldHookConfig<string> & ICustomFieldProps> = ({ 
  label,
  ...props
}) => { 
...
}

// extending
interface ICustomFieldProps extends JSX.IntrinsicElements['input'] {
  label: string;
  name: string;
}

export const CustomInput: FC<ICustomFieldProps> = ({ label, ...props }) => { 
...
}
```
Choose whatever way you like. 

Now let's create our form. Open `pages/index.tsx` file, delete everything in between `div` and replace `div` with `Formik` component.  
Add some dummy `initialValue` and `onSubmit` to `Formik` component, place `<Form>` and `<CustomInput />` inside `<Formik>`: 
```javascript
const Home: NextPage = () => {
  return (
    <Formik initialValues={{name:''}} onSubmit={(val) => alert(val.name)}>
      <Form>
        <CustomInput name='name' label='name' />
        <Button type='submit'>Submit</Button>
      </Form>
    </Formik>
  )
}

export default Home
``` 

Ta-da. We now have a form built with Chakra UI and managed by Formik. 

But. If you're using NextJS and you open devtools console, you will see a big fat error: ```Warning: Prop `id` did not match.```

We can fix this by adding `id` to our form elements explicitly. Inside `CustomInput.tsx` add `id` attribute to `Input` and `FormLabel` components and `htmlFor` attribute to `FormLabel`:
```javascript
export const CustomInput: FC<IFormikFieldProps & FieldHookConfig<string>> = ({
  label,
  ...props
}) => {
  const [field, meta] = useField(props);

  return (
    <FormControl isInvalid={meta.touched && !!meta.error}>
      <FormLabel
        id={`${props.id}-${props.name}-label`}
        htmlFor={`${props.id}-${props.name}-input`}
      >
        {label}
      </FormLabel>
      <Input 
        {...field}
        id={`${props.id}-${props.name}-input`} 
      />
    </FormControl>
  );
};
```

Cool. It's done. 

##### notes: 
- for select, you must pass `props.children` to Chakra UI's `Select` component explicitly: 
```javascript
<Select {...field} id={`${props.id}-${props.name}-select`}>
  {props.children}
</Select>
```
- you can add `FormErrorMessage` component, too

Thanks for reading =)