Home > Blockchain >  How do you use react-hook-form with redux and typescript properly
How do you use react-hook-form with redux and typescript properly

Time:06-03

After many days of trying to find a guide that would show me all of these things in one example I finally gave up and made something that works via brute force, but I know that this is the wrong way to do it. I don't know exactly why it's wrong but it cant be as complicated as I have made it out to be here. These are my requirements:

  1. use Redux to maintain application state
  2. use React Hook Form for forms
  3. use Typescript
  4. forms must have client side and server side validation ability

Here is what I have:

import * as React from "react";
import { useForm } from "react-hook-form";
import { ErrorMessage } from '@hookform/error-message';
import { connect } from "react-redux";

import { MainMenu } from "./../components/MainMenu";

import { userSignupAction } from '../actions/authenticateActions';

interface FormInputs {
  singleErrorInput: string;
  userName: string;
  password: string;
  email: string;
  firstName: string;
  lastName: string;
}

export function Signup(props: any) {
  const { 
    register,  
    formState: { errors },
    setError,
    handleSubmit,
    clearErrors
  } = useForm<FormInputs>();

  const onSubmit = handleSubmit(async (data: FormInputs) => {
    props.dispatch(userSignupAction(data));
  });

  if (props.response.signup.userName) {
    if (!errors.userName) {
      console.log('set username error')
      setError('userName', {
        type: "server",
        message: props.response.signup.userName[0],
      });
    }
  }

  if (props.response.signup.email) {
    if (!errors.email) {
      setError('email', {
        type: "server",
        message: props.response.signup.email[0],
      });
    }
  }

  if (props.response.signup.password) {
    if (!errors.password) {
      setError('password', {
        type: "server",
        message: props.response.signup.password[0],
      });
    }
  }

  return (
    <div>
      <MainMenu role="anonymous"/>
      <div className="center70">
        <h2>Signup</h2>
        <form onSubmit={onSubmit}>
          <div>
            <label>Username</label>
            <input {...register("userName", { required: "Username is required"})}
              onClick={() => {
                console.log('clear the error');
                props.response.signup.userName = false
                clearErrors('userName');
              }} />
            { errors.userName && <ErrorMessage errors={errors} name="userName" />}
          </div>
          <div>
            <label>First Name</label>
            <input {...register("firstName")} />
          </div>
          <div>
            <label>Last Name</label>
            <input {...register("lastName")} />
          </div>
          <div>
            <label>Email</label>
            <input {...register("email", { required: "Email is required"})} />
            <ErrorMessage errors={errors} name="email" />
          </div>
          <div>
            <label>Password</label>
            <input {...register("password", { required: "Password is required"})} />
            <ErrorMessage errors={errors} name="password" />
          </div>
          <input type="submit" />
        </form>
      </div>
    </div>
  );
}

const mapStateToProps = (response: any) => ({
  response
});

export default connect(mapStateToProps)(Signup);

Note, this is not me trying to get an answer for my job, this is just me trying to learn this technology correctly. This code is all my code and not part of any proprietary application (clearly as it is not good)

Please help

More info...

My first challenge here was in trying to get a server side validation failure to show up on the front end. The way I tried to do this initially was by formatting the response as a json object exactly the way the error object is supposed to look in react-hook-forms, like so...

{
  "userName": ["User 'jimbob' already exists"],
  "email": ["User with email address '[email protected]' already exists"],
  "password": ["Password must be at least 6 characters"]
}

At first I was expecting this response format to magically populate the error but it did not so this is why I had to add the three clauses to check the props value. Initially this caused an infinite loop so I had to add the second check to see if errors.fieldName was not truthy and only setError in those cases (great! I thought)

But then the next problem was, what happens when you get a server-side validation error but then the user goes back to fix it. Well in my opinion this should immediately clear the error the moment the user clicks on the field. Well this wasn't happening because even though the clearErrors function did it's job, the props check just immediately undid it so i had to add the extra line to set the field in props to false. yay! now it works, but at what cost?

This code all technically works now, but man is this a wordy piece of code for such a simple form. I can't believe that this is the right way to do things, it shouldn't take 100 lines of code to make a simple 5 element form.

CodePudding user response:

If you are creating react app with the help of react-redux library, there are built-in hooks for,

  1. Get the data from the redux state - useSelector()
  2. And dispatch actions to update the redux state - useDispatch()

Usage of useSelector() :-

So, you don't need to use following code to map redux state to props.

const mapStateToProps = (response:any) => ({
    response
});

Simply, you should import useSelector() from react-redux as follows.

import {useSelector} from 'react-redux';

Then you can get whatever data from the redux state to your component.

const user = useSelector(state => state.user);

Usage of useDispatch() :-

You can use useDispatch() instead of using props to get dispatch(). This hook returns a reference to the dispatch function from the Redux store. You may use it to dispatch actions as needed.

import {useSelector, useDispatch} from 'react-redux';
const dispatch = useDispatch();

You can use dispatch reference wherever you want to dispatch a action, for example in your code instead of using following code,

const onSubmit = handleSubmit(async (data: FormInputs) => {
    props.dispatch(userSignupAction(data));
});

you can use dispatch reference that we created as follows.

const onSubmit = handleSubmit(async (data: FormInputs) => {
    dispatch(userSignupAction(data));
});

CodePudding user response:

Your existing flow is currently as follows. Submit form -> setResponse -> re-render -> read response -> set errors.

A couple things to note.

  1. You should not be using a client side validation tool (ReactHookForm) for server side validation/errors
  2. Do not setError within the render method of the component
  3. The async wrapper around dispatch is redundant
  4. Re evaluate your requirements, because they aren't sensible.

A better flow would be either having your own redux middleware or utilise redux thunk. Change the flow to be: Submit -> Check for errors -> setErrors via callback -> dispatch response -> set and display response

  • Related