Chakra + Form Hook + Number Format

Roque
4 min readMay 8, 2022

--

Image by chakra-ui.com

Hello! Those days I ended up losing a whole day trying to make a form with ChakraUi that used Number Format and Form Hooks, so I will write this post so that people who end up here don’t need to go through the same problem!

The code is available on CodeSandbox, so feel free to check that if you want!

I hope this article to be a fast reading, so I’ll assume that you have your project setup done, and I’ll also focus only on the file that keeps the form! But don’t forget to add Number Format and React Hook Form to your project!

  • yarn add react-hook-form
  • yarn add react-number-format

I’m using yarn just because I like it more, but feel free to use npm, I’m also using TypeScript.

First Step

We’ll start by creating the component with the form, we’re not going to use form hook yet. Our form will have 2 fields, name of the product and the Price, you may suspect that, but we’re making a form that can be used to register a product!

ChakraUi gives us some very good building tools for forms, so we’ll use them! Each field will have a FromControl, FormLabel, FormErrorMessage and the input field that you want.

Our field that captures the name of the product will look like this:

<form>
...
<FormControl>
<FormLabel htmlFor="name">Name</FormLabel>
<Input id="name" placeholder="Your Name" />
<FormErrorMessage></FormErrorMessage>
</FormControl>
...
</form>

We can follow the same structure to our other fields.

Note that FormErrorMessage is empty, we’ll deal with that when we use react-hook-form.

Second Step

We’ll now use Number Format do make input masks. Why? I’ts a better user experience that way, and a simple but good way to make your form better.

You frst import Number format in your component

import NumberFormat from 'react-number-format';

Then, you use your Input from Chakra as a Number format, this way:

<form>
...
<FormControl>
<FormLabel htmlFor="price">Price</FormLabel>
<Input
as={NumberFormat}
id="price"
placeholder="Price"
prefix={'$'}
thousandSeparator=','
decimalSeparator='.'
/>
<FormErrorMessage></FormErrorMessage>
</FormControl>
...
</form>

Third Step

We now need to add our hook, that’s the tricky part. First of all, we need to create a state to keep track of the value, the motive for that is simple, we don’t want to save our input with a mask, we want just the number. The state will solve that.

const [priceState, setPriceState] = useState(0)

Now, we import react-hook-form in our component:

import { useForm, Controller } from 'react-hook-form'

You can see that we imported controller besides the hook, we’ll use it to control how the hook will get the value from the input, since we can’t just use the hook inside the NumberFormat, the way we do it is simple, we use the controller as a “wrap” for our input, and with that the form will get the value from the controller instead of the input, he’s our” middleman”.

const {
handleSubmit,
register,
control,
formState: { errors, isSubmitting },
} = useForm()

The code above shows how to get the hook started, note that ‘control’ is there, we’re gonna use it now.

<form>
...
<FormControl>
<FormLabel htmlFor='entryValue'>Price</FormLabel>
<Controller
control={control}
name="price"
{...register('price', {
required: 'This is required'
})}
render={({ field: { onChange, name, value } }) => (
<Input as={NumberFormat}
thousandSeparator=','
decimalSeparator='.'
prefix={'$'}
value={value}
onChange={onChange}
onValueChange={(values:any) => {
const { formattedValue, value } = values;
setPriceState(value);
}}
/>
)}
<FormErrorMessage></FormErrorMessage>
</FormControl>
...
</form>

The important part happens in onValueChange! At this point we have two values, the formatted one, and the one who isn’t formatted, that our state will keep

If you’re asking about the other field, product name, he is more simple to use, we just need to use register with it!

<form>
...
<FormControl>
<FormLabel htmlFor="name">Name</FormLabel>
<Input id="name" placeholder="Your Name"
{...register('name', {
required: 'This is required'
})}
/>
<FormErrorMessage></FormErrorMessage>
</FormControl>
...
</form>
The “required” part will be used to handle errors, like if a field wasn’t filled, there are more options to use, like maximum length, so feel free to test it out.

Fourth Step

Now we can add error handling in our form! We react-form-hook keeps track of any error that happens in a field that it has registered, so we can use it on FormErrorMessage and FormControl this way:

<form>
...
<FormControl isInvalid={errors.name}>
...
<FormErrorMessage>
{errors.price && errors.cpf.message}
</FormErrorMessage>
</FormControl>
...
</form>

Final Step

With all of that done, the only thing left it’s to handle the submit, we’ll create a function that receives

The values from handle submit (the react-hook-form, remember?) and update the value for price that it has with the one that our state have

function onSubmit(values:any) {
return new Promise<void>((resolve) => {
setTimeout(() => {
values.price = priceState
alert(JSON.stringify(values, null, 2))
resolve()
}, 1000)
})
}

Now we just need to make our form use this function on submit and create our submit button!

<form onSubmit={handleSubmit(onSubmit)}>
...
<Button type="submit">Submit</Button>...
</form>

Wraping up

The full code is on CodeSandbox, I really lost a full evening trying do do this on my on, so I hope to help some poor soul like me. Have a nice time, stay safe, and if you need something, just call me 😄

--

--

Roque

I’m a Full Stack developer who loves to learn something new, bearer of a very simple brain, trying to write things that are easy and as failproof as possible