Simple package that makes working with Chakra UI
and formik
together -- easier. This package uses
UI form components from Chakra UI
and turns them into controlled components using formik
state. The styling
of the components is 100% extensible. The package does not add additional style on top of the Chakra UI
styling.
- Cleaner code
- 100% extensible styling
- Some caveats from using
Chakra UI
components andformik
together are handled - Possibility to add
formik
validation functions as props to the form components - Reusability
- Floating label
The main motivation behind this package was to minimize the repetition that happens when working with forms with Chakra UI
and the usage of FormControl
. That, together with the repetition of useField
when working with formik
brought this project to life. Example:
Before chakra-fields
:
import {
FormControl,
FormErrorMessage,
FormLabel,
NumberInput,
NumberInputField,
Input
} from '@chakra-ui/react';
import { useField } from 'formik';
const [fullNameField, fullNameMeta] = useField<string>('fullName');
const [ageField, ageMeta, ageHelpers] = useField<string>({
name: 'age',
validate: (value: string) => {
return Number(value) < 18 ? 'Must be above 18' : undefined;
}
});
<FormControl isRequired={true} isInvalid={!!fullNameMeta.error && fullNameMeta.touched}>
<FormLabel>Full name: </FormLabel>
<Input {...fullNameField}/>
<FormErrorMessage>{fullNameMeta.error}</FormErrorMessage>
</FormControl>
<FormControl isRequired={true} isInvalid={!!ageMeta.error && ageMeta.touched}>
<FormLabel display="inline" w="20%">Age: </FormLabel>
<NumberInput
{...ageField}
w="80%"
min={0}
onChange={(valueAsString: string) => {
ageHelpers.setValue(valueAsString);
}}
>
<NumberInputField/>
</NumberInput>
<FormErrorMessage>{ageMeta.error}</FormErrorMessage>
</FormControl>
Looks a little bit too much doesn't it? Let's fix that:
import { NumberInputField } from '@chakra-ui/react';
import { NumberField, TextField } from '@codechem/chakra-fields';
<TextField
name="fullName"
label="Full name: "
isRequired={true}
/>
<NumberField
name="age"
w="80%"
min={0}
isRequired={true}
label="Age: "
labelProps={{ display: 'inline', w: '20%' }}
validate={(value: string) => {
return Number(value) < 18 ? 'Must be above 18' : undefined;
}}
>
<NumberInputField/>
</NumberField>
Much better! Everything in one place where it belongs. No more multiple lines of useField
and renaming the field
, meta
and helpers
variables. As you can see, even if the signatures of onChange
between formik
and Chakra UI
don't match, as was the case for NumberInput
where you have to manually set the value and you kinda loose the purpose of formik
, with chakra-fields
you don't have to worry about that because it's already handled. No more painful repetition of FormControl
, FormLabel
, FormErrorMessage
and setting isInvalid
flag with the same line of code to each input -- now that comes automatically with the chakra-fields
components.
$ npm i @codechem/chakra-fields
The following components wrap the standard Chakra UI
form inputs with the FormControl
component, while handling state, validation and error messages with formik
. First see FormControlField
.
TextField
-> wrapper aroundChakra UI
'sInput
component. Used usually fortext
,password
,email
,date
,datetime-local
input types. Theformik
state holds astring
value for this field. For styling you can set all style props that you would set on aInput
component. See also Chakra UI Input docs.TextareaField
-> wrapper aroundChakra UI
'sTextarea
component. Theformik
state holds astring
value for this field. For styling you can set all style props that you would set on aTextarea
component. See also Chakra UI Textarea docs.NumberField
-> wrapper aroundChakra UI
'sNumberInput
component. Theformik
state holds astring
value for this field. For styling you can set all style props that you would set on aNumberInput
component. See also Chakra UI NumberInput docs.SelectField
-> wrapper aroundChakra UI
'sSelect
component. Theformik
state holds astring
value for this field. For styling you can set all style props that you would set on aSelect
component. See also Chakra UI Select docs.CheckboxField
-> wrapper aroundChakra UI
'sCheckbox
component. This component should be used as a single checkbox component. Theformik
state holds aboolean
value for this field. For styling you can set all style props that you would set on aCheckbox
component. See also Chakra UI Checkbox docs.RadioGroupField
-> wrapper aroundChakra UI
'sRadioGroup
component. The group is composed of multiple radio buttons. UseRadio
fromChakra UI
orRadioGroupField.Item
(alias toRadio
) as components for the radio buttons. Theformik
state holds astring
value for this field. For styling you can set all style props that you would set on aRadioGroup
component. See also Chakra UI RadioGroup docs.InputGroupField
-> wrapper aroundChakra UI
'sInputGroup
component. The group is composed from one input element (Input
/NumberInput
) and one or multiple right/left addons/elements. See Chakra UI docs. The difference here is that instead of using the nativeChakra UI
input element as part of the group, theInputGroupField.Input
orInputGroupField.NumberInput
must be used instead. Theformik
state holds astring
value for this field. For styling you can set all style props that you would set on aInputGroup
component. See also Chakra UI InputGroup docs.CheckboxGroupField
-> this is actually a new component and does not wrap any existingChakra UI
component, but it behaves similarly to theCheckboxGroup
component. This group consists of multiple choice checkboxes and the component to be used for them must beCheckboxGroupField.Item
. Theformik
state holds a(string | number)[]
value for this field. For styling you can set all style props that you would set on aCheckboxGroup
component. See also Chakra UI CheckboxGroup docs.FormControlField
-> wrapper aroundChakra UI
'sFormControl
component. It is used to wrap the previously listed fields. This component does not holdformik
state on its own and it expects that there is already a field defined, i.e. state, with the given name in theformik
context when used. It is not recommended to be used, it is more for internal use.
The following props can be used on all components:
name
- required prop and it should be unique in its enclosingFormik Context
isRequired
,isReadOnly
,isDisabled
-FormControlOptions
that will be forwarded to theFormControl
. If we setisInvalid
it will have no effect because that prop is set automatically from theformik
validation.validate
- validation function thatformik
will use to validate the fieldlabel
- contents of theFormLabel
for the fieldlabelProps
- custom props for theFormLabel
for fine grained stylinglabelPosition
- where the label would be displayed:before
,after
orfloating
. By default is set tobefore
and the label is displayed in the row before the input field/group.floating
is not available for groups andCheckboxField
.errorMessageProps
- custom props for theFormErrorMessage
for fine grained styling
NOTE: all style props that we set to the components will be forwarded to their respective Chakra UI
component that it wraps. Example: the style props we set to SelectField
will be forwarded to Select
from Chakra UI
, TextField
to Input
, NumberField
to NumberInput
, etc.
First there is a need of a Formik Context
that will wrap all of the chakra-fields
components.
NOTE: Internally the components use useField
that expects to be provided a Formik Context
and would fail if not provided.
import { Form, Formik } from 'formik';
<Formik
initialValues={{} as Values}
onSubmit={(values: Values) => {}}
>
<Form>{/* chakra-fields components go here */}</Form>
</Formik>
import { TextField } from '@codechem/chakra-fields';
<TextField name="username" label="Username" labelPosition="floating"/>
<TextField name="password" type="password" label="Password" labelPosition="after"/>
<TextField
name="dob"
type="date"
label="Date Of Birth"
min="1998-11-07" // style props are forwarded to the `Input` component
w="80%" // style props are forwarded to the `Input` component
variant="outline" // style props are forwarded to the `Input` component
/>
import { TextareaField } from '@codechem/chakra-fields';
<TextareaField
name="comment"
isRequired={true} // field is required (forwarded to `FormControl` as well)
w="80%"
validate={(value: string) => {
return value.length < 30 ? 'Comment too short' : undefined;
}}
errorMessageProps={{ color: 'yellow' }} // forwarded to `FormErrorMessage`
/>
import {
NumberDecrementStepper,
NumberIncrementStepper,
NumberInputField,
NumberInputStepper
} from '@chakra-ui/react';
import { NumberField } from '@codechem/chakra-fields';
<NumberField name="age" label="Age" isDisabled={true}>
<NumberInputField/>
</NumberField>
<NumberField name="amount" label="Payment Amount" precision={2} min={0} step={100.50}>
<NumberInputField/>
<NumberInputStepper>
<NumberIncrementStepper/>
<NumberDecrementStepper/>
</NumberInputStepper>
</NumberField>
import { SelectField } from '@codechem/chakra-fields';
<SelectField name="year" placeholder="Select year of studies">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</SelectField>
import { CheckboxField } from '@codechem/chakra-fields';
<CheckboxField
name="terms"
isRequired={true}
label="Terms and Conditions"
labelProps={{ display: 'inline' }}
>
I agree
</CheckboxField>
import { Radio, HStack } from '@chakra-ui/react';
import { RadioGroupField } from '@codechem/chakra-fields';
<RadioGroupField name="favoriteShow" label="Favorite Show?" colorScheme="green">
<HStack>
<RadioGroupField.Item value="1">The Office</RadioGroupField.Item>
<RadioGroupField.Item value="2">Brooklyn 99</RadioGroupField.Item>
<Radio value="3">Stranger Things</Radio> {/* Either `Radio` or `RadioGroupField.Item` can be used */}
</HStack>
</RadioGroupField>
import {
NumberDecrementStepper,
NumberIncrementStepper,
NumberInputField,
NumberInputStepper,
InputLeftElement,
InputLeftAddon
} from '@chakra-ui/react';
import { InputGroupField } from '@codechem/chakra-fields';
<InputGroupField name="salary" label="Salary Amount">
<InputLeftElement pointerEvents='none' color='gray.300' fontSize='1.2em' children='$'/>
<InputGroupField.NumberInput precision={2} step={100.50} min={0}> {/* `InputGroupField.NumberInput` must be used, not `NumberInput` from `Chakra UI` */}
<NumberInputField pl={10}/>
<NumberInputStepper>
<NumberIncrementStepper/>
<NumberDecrementStepper/>
</NumberInputStepper>
</InputGroupField.NumberInput>
</InputGroupField>
<InputGroupField name="telephone" label="Telephone number">
<InputLeftAddon children='+234'/>
<InputGroupField.Input type='tel'/> {/* `InputGroupField.Input` must be used, not `Input` from `Chakra UI` */}
</InputGroupField>
import { HStack } from '@chakra-ui/react';
import { CheckboxGroupField } from '@codechem/chakra-fields';
<CheckboxGroupField
name="toppings"
label="Chose one or more toppings"
colorScheme="green"
validate={(values: (string | number)[]) => {
return values.length === 0 ? 'You must select at least one' : undefined;
}}
>
<HStack>
{/* `CheckboxGroupField.Item` must be used, not `Checkbox` from `Chakra UI` */}
<CheckboxGroupField.Item value={1}>Pepperoni</CheckboxGroupField.Item>
<CheckboxGroupField.Item value={2}>Pineapple</CheckboxGroupField.Item>
<CheckboxGroupField.Item value={3}>More cheese</CheckboxGroupField.Item>
</HStack>
</CheckboxGroupField>
WARNING: not recommended for use. Use the above listed fields which use this field internally
import { Input } from '@chakra-ui/react';
import { useField } from 'formik';
import { FormControlField } from '@codechem/chakra-fields';
const [emailField] = useField<string>('email');
<FormControlField name="email" label="Email: ">
<Input {...emailField} type="email"/>
</FormControlField>
// access a field value within a given formik context
const formikContext = useFormikContext<Values>();
const { value } = formikContext.values;
// react on a change
const formikContext = useFormikContext<Values>();
const { value } = formikContext.values;
useEffect(() => {
// do something on `value` change. Ex:
formikContext.setFieldValue('otherField', value.toUpperCase());
}, [value]);
// or
<TextField name="value" onChange={(e) => {
// do something on `value` change. Ex:
formikContext.setFieldValue('otherField', e.target.value.toUpperCase());
}}/>
See also the formik
docs for useFormikContext
and Formik
to see what are the capabilities and how you can use them here.
The folder /examples
contains a React
application that has chakra-fields
as a dependency. The application displays the same form two times, the difference being that one is built with chakra-fields
and the other is built without it. See /examples/src/chakra-fields-example.tsx
to see the form built with chakra-fields
and /examples/src/native-chakra-example.tsx
to see the form built without chakra-fields
. The main thing to notice here is the difference in developing the same form with and without chakra-fields
.
- Dejan Slamkov, GitHub