Home > OS >  how to use Axios to submit form data only when form is correctly filled out
how to use Axios to submit form data only when form is correctly filled out

Time:11-28

Background Information:

So I'm working on a project that includes sending form data to a database. For now I'm just using a Mock API to get everything up and running before setting up the database in .NET.

Custom Hook and Validator information:

I'm using a custom hook with 2 main functions: handleChange() which is responsible for setting the new values on the main component, CreateUser, and handleSubmit(), which calls on my custom validator (validateNewUser) to check for errors in the form.

My hook and validator are working fine, but I'm having issues with the Axios post request. I am able to place my Axios post request in my custom hook and it works as intended, but I cannot figure out how to send the form to the Mock API only when the form is correctly filled out.

Trials and Errors so far:

I have tried using an if statement in my handlePost function (which I think is the correct way to do this, I could just be missing something, I could be wrong), I tried putting the Axios request inside of a handlePost function but that yielded no progress.

The way I have it set currently works, but basically anytime you click submit it sends the data to the API, most likely because it submits the form every time. The other way I could do it is possibly setting the errors to pop up as the user fills out the form? So that the form is accurate before it even is submitted.

I'm pretty new to React so I apologize if my question/issue is confusing. If more information is needed I will provide what I can.

The code for all parts involved will be below, and any and all input is greatly appreciated.

Main Code:

useCreateUser.jsx:

import { useState } from 'react';
import axios from 'axios';

const useCreateUser = (validateNewUser) => {
    const [values, setValues] = useState({
        firstName: '',
        lastName: '',
        email: '',
        password: ''
    });

    const [errors, setErrors] = useState({});

    const handleChange = e => {
        const {name, value} = e.target
        setValues({
            ...values,
            [name]: value
        });
    };

    const handleSubmit = e => {
    e.preventDefault();
    setErrors(validateNewUser(values));

    const user = {
        firstName: values.firstName,
        lastName: values.lastName,
        email: values.email,
        password: values.password
        };

    if (handleSubmit){
        axios.post(url, user)
        .then((response) => {
        console.log(response.data);
    })
    }
}

  return {handleChange, values, handleSubmit, errors}
}

export default useCreateUser;

validateNewUser.jsx:

export default function validateNewUser(values) {
    let errors = {}

    //Validate First Name
    if(!values.firstName.trim()) {
        errors.firstName = 'First Name is Required.'
    }

    //Validate Last Name
    if(!values.lastName.trim()) {
        errors.lastName = 'Last Name is Required.'
    }

    //Validate Email
    if(!values.email){
        errors.email = 'Email is Required.'
    } else if(!/\S @\S \.\S /.test(values.email)) {
        errors.email = 'Invalid Email Address.'
    }

    if(!values.password){
        errors.password = 'Password is Required.'
    } else if(values.password.length < 6) {
        errors.password = 'Password must be greater than 6 characters.'
    }

    return errors;

}

CreateUser.jsx:

import Header from "../Header";
import { Form, Button } from "semantic-ui-react";
import { Box } from "@mui/material";
import useCreateUser from "../../hooks/useCreateUser";
import validateNewUser from "../../validators/validateNewUser";

const CreateUser = () => {
    const {handleChange, values, handleSubmit, errors} = useCreateUser(validateNewUser);
    
    return (
    <Box m="20px">
        <Box 
        display="flex" 
        justifyContent="space-between" 
        alignItems="center"
        margin="0 auto"
        marginBottom="20px"
        >
            <Header title="Create a New User" subtitle="Please ensure to select the correct role."/>
        </Box>

        <Form onSubmit={handleSubmit}>
            <Form.Field>
                <label>First Name</label>
                <input placeholder='First Name' name='firstName' type='text' value={values.firstName} onChange={handleChange} />
                {errors.firstName && <p style={{color: "red"}}>{errors.firstName}</p>}
            </Form.Field>
            <Form.Field>
                <label>Last Name</label>
                <input placeholder='Last Name' name='lastName' type='text' value={values.lastName}  onChange={handleChange} />
                {errors.lastName && <p style={{color: "red"}}>{errors.lastName}</p>}
            </Form.Field>
            <Form.Field>
                <label>Email</label>
                <input placeholder='Email' name='email' type='email' value={values.email} onChange={handleChange} />
                {errors.email && <p style={{color: "red"}}>{errors.email}</p>}
            </Form.Field>
            <Form.Field>
                <label>Password</label>
                <input placeholder='Password' name='password' type='password' value={values.password} onChange={handleChange} />
                {errors.password && <p style={{color: "red"}}>{errors.password}</p>}
            </Form.Field>
            <Button type='submit'}>Submit</Button>
        </Form>
    </Box>
    )

}


export default CreateUser;

CodePudding user response:

Few pointers It is better to give feedback to the user as he enters the data

// validate on change
const handleChange = e => {

    const { name, value } = e.target;

    const updatedValues = { ...values, [name]: value };

    setValues(updatedValues)

    const validationErrors = validateNewUser(updatedValues);

    setErrors(validationErrors)

};

On Submit you can return the first error, so that the form can scroll or focus on the field with the error.

const handleSubmit = async (e) => {

    e.preventDefault();
  
    // this might  not be needed
    // you can do validations which need api calls
    // but event those can be done on input
    // setErrors(validateNewUser(values));

    // return error code
    const firstErrorKey = Object.keys(error)[0]
    if (firstErrorKey !== undefined) {
       return { hasError: true } 
    }

    const user = {
        firstName: values.firstName,
        lastName: values.lastName,
        email: values.email,
        password: values.password
    };

    const response = await axios.post(url, user)
    console.log(response.data);

    // return success code depending on server response
    // so u can show success
    // return { isSuccess: response.data?.code === 0 } 

    }
}

in form you can set a loader set a loader

 const onFormSubmit = async e => {
   try {

      setState('submitting')

      const response = await handleSubmit(e);

      if (response.isSuccess) {
        // show alert/toast
      }

     if (response.hasErrors) {
       // focus on first error control
        const firstErrorKey = Object.keys(error)[0]
        if (firstErrorKey !== undefined) {
           // focus on control with error
        }
     }

      setState('success')

   } catch(error) {
     setState('error')
   }
 }

 <Form onSubmit={handleSubmit}>

 </Form>

If you can seperate the form input state from api logic it will make the code more maintainable.

If possible use libraries like Formik, they handle the form state and allow you to focus on the business part of the code, like api call, validations

Thinking of component states will also help you design better components

Hope it helps in some way,

Cheers

  • Related