Posts

  • Validation in react-hook-form with nested form fields

    In the projects I have worked on forms quickly grow to be large components. The big size introduces the need for splitting the form into smaller components. Previously I passed down the register method to the child components because I used the validation option of the register method. Now that I have tested out schema validation I can safely say that schema validation is the better option for nested form fields.

    TL;DR: Use schema validation in react-hook-form for forms with nested form fields.

    From basic to schema validation

    Following are code examples where we go from a basic form in one component to a form that consists of several components and uses schema validation.

    First we have a simple example with react-hook-form using native elements:

    <form>
      <label for="inputId">Name</label>
      <input
        type="text"
        id="inputId"
        {...register("name", { required: true, maxLength: 32 })}
      />
    </form>;
    

    Using version 7 of react-hook-form one must spread the register method into the input element, and like in old versions we can specify validation rules as the second argument.

    I rarely use the native HTML-input elements directly and instead use a component library like react-bootstrap. Then a single input becomes the following:

    <Form>
      <Form.Group controlId="inputId">
        <Form.Label>Name</Form.Label>
        <Form.Control
          type="text"
          {...register("name", { required: true, maxLength: 32 })}
        />
      </Form.Group>
    </Form>;
    

    And it is pretty much essential to have feedback on the field if there are validation errors so let’s add that. And if we also add some of the usual HTML attributes we end up with this:

    <Form>
      <Form.Group controlId="inputId">
        <Form.Label>Name</Form.Label>
        <Form.Control 
          autoFocus 
          type="text" 
          {...register("name", { required: true, maxLength: 32 })}
          isValid={!errors?.name} 
          isInvalid={!!errors?.name} 
          defaultValue="" 
          placeholder="Write your name here" 
        />
        <Form.Control.Feedback type="invalid">
          {errors?.name?.message}
        </Form.Control.Feedback>
      </Form.Group>
    </Form>
    

    This is only one input field! So it is not surprising the forms become quite large. At this point we probably want to move the repeated JSX into a reusable component. I often put the Form.Group (with the code inside it) into a reusable component called FormField.

    The example below is how I did nested form fields before; you may skip it. I also added the useForm hook explicitly because I think it makes it clearer.

    // Bad code, react-hook-form 6
    const { register, handleSubmit } = useForm();
    
    return (
      <Form onSubmit={handleSubmit((data) => { console.log(data) })}>
        <FormField
          labelText="Name"
          errors={errors}
          inputRef={register("name", { required: true, maxLength: 32 })}
        />
      </Form>
    );
    

    As seen above, the register method is passed down from the parent component to the child component (FormField), which will set it on the input ref. This doesn’t work with react-hook-form version 7 because it requires one to apply spread to the register method1.

    One solution to make the above work in version 7 is to use schema validation. There a numerous schema libraries supported, see all of them in the resolvers repository. I went with zod because it is able to infer the form type from the schema.

    // Good code 1, react-hook-form 7
    // import { z } from "zod";
    // import { zodResolver } from "@hookform/resolvers/zod";
    
    const formSchema = z.object({
      name: z.string().max(32),
    });
    const FormType = z.infer<typeof formSchema>;
    
    const { register, handleSubmit } = useForm<FormType>({
      resolver: zodResolver(formSchema),
    });
    
    return (
      <Form onSubmit={handleSubmit((data: FormType) => { 
          console.log(data) 
        })}
      >
        <FormField
          name="name"
          labelText="Name"
          errors={errors}
          register={register}
        />
      </Form>
    );
    

    Because of the fact that validation with schema will happen after all the inputs have been registered, we only need to register the field inside the FormField component; {...register("name")}, not pass any validation. And by the way, I added imports in comments so you can see where z and zodResolver are coming from.

    And lastly, I tend to use the FormContext API right away so I don’t need to pass the register method down in all children.

    // Good code 2, react-hook-form 7
    // import { z } from "zod";
    // import { zodResolver } from "@hookform/resolvers/zod";
    
    const formSchema = z.object({
      name: z.string().max(32),
    });
    const FormType = z.infer<typeof formSchema>;
    
    const formMethods = useForm<FormType>({
      resolver: zodResolver(formSchema),
    });
    const { register, handleSubmit } = formMethods;
    
    return (
      <FormProvider {...formMethods}>
        <Form onSubmit={handleSubmit((data: FormType) => { 
            console.log(data) 
          })}
        >
          <FormField name="name" labelText="Name" errors={errors} />
        </Form>
      </FormProvider>
    );
    

    Now you know how to use schema validation with react-hook-form so you can manage large forms with ease. ✅🚀

    Gotcha: input element always return string

    When you write your zod schema to validate something else than a string type you will experience that it doesn’t work. This is because the native HTML-input field always returns a string! React-hook-form has two quick options you can use that will try to convert the value to another type; valueAsNumber and valueAsDate on the register method. If the field is optional, though, you should instead use the setValueAs method and convert from string yourself. I wrote an example on how to have optional number inputs with react-hook-form and zod on GitHub.

    Example of FormField component

    If you want an example on how the FormField component can look like here it is! ❤


    Last updated December 17, 2021

    Comments on this text? Create an issue on Github!

    1. Of course one could allow the custom component to receive any props but I don’t like that since one loses the type safety on the props. 

  • A sensible merge default for PRs

    There are many git branching strategies out there. Popular alternatives I have come across are: git-flow, GitHub flow and GitLab flow. We will not discuss which git branching strategy to use, but the smaller problem; what kind of merge should I do when merging my pull requests (PRs). You might have noticed these options at your git host when merging a pull request:

  • Creating an Alias game online

    For å spille Alias følg denne linken: stianjo.no/also-known-as

  • Prediction: front- and back end, same codebase

    I was just thinking about the current practice of writing front end and back end code separately, and I wouldn’t be surprise if that is starting to change. Evolution in software development seems like an endless circle of getting a new advanced feature and then making it easier to use. At consultant firms in Norway for the last 10 years, I believe it was common to deliver a back end in Java/C#, and a front end in JS. The benefits of having a dedicated back end can be many; I can mention security and performance as two factors. The benefits of writing one codebase is that it potentially can be much faster, and increased productivity has been the winning factor for hundreds of years. My realisation is that having back end and front end (running on two different machines) isn’t a hinder for having one codebase. When I write “one codebase” in this post, I don’t mean that it is an application running solely on a client or on a server. Just running code on one of the sides (client or server) is a completely viable solution for many problems and to ensure one codebase, but in this post I argue that it is possible to have one codebase running on both sides.

  • Developing MagicMirror on Windows

    I have been developing my custom modules for the Magic Mirror software on my old laptop. Windows is the operating system, but I have been using Windows Subsystem for Linux (WSL) to develop it. I assume I didn’t get it to run on Windows when I started, but people have done it so I should try it out again soon. Anyway, on my old laptop I managed to run Magic Mirror in WSL 1, which I could not reproduce on my stationary… so for future reference, here is how I run Magic Mirror software on Windows in WSL 2.

  • What is Webassembly

    Or rather, what is the deal with Webassembly (WASM)? We already know JavaScript, CSS and HTML are used on the web, and there are tons of different libraries we can use. So what about WebAssembly?

  • How to create a custom Guess Who game

    A colleague told me that a custom Guess Who game was one of the best gifts he had given to his girlfriend. He swapped out the cards with images of family and friends and said it was a ton of fun to play. I was inspired to create one for my girlfriend’s birthday. This post explains how I created a custom Guess Who game using React, and here’s a link to the repo with the code so you can create your own!

  • Shortcomings of Microsoft Teams

    I use Microsoft Teams at work, and this little post is where I explain how it could be better (for me). The communication tools I am comparing MS Teams to are the ones I have used and is fit for business; Skype and Slack.

  • Printing correct size using CSS

    On one personal project of mine I wanted to print a box (a HTML div) on paper with the specific dimensions 32mm x 35mm. Said again a little differently, I wanted the box on the paper to be 32mm x 35mm in our real physical world. And, I wanted to use CSS to design that box.

  • Why 5cm in CSS may not be 5cm on screen

    Measuring a screen

  • Smartspeil del 1: Maskinvaren

    Denne posten beskriver med bilder hvordan jeg bygget et smartspeil.

subscribe via RSS