Handling errors with React and Laravel

Murilo Livorato
6 min readJan 7, 2024

--

Today, I will share with you the way that I handle erros with React + Laravel .

Handling errors from a Laravel API in a React application involves making API requests, capturing potential errors, and displaying meaningful messages to the user. Below is a step-by-step tutorial to guide you through this process:

Step 1: Set up Laravel API

Make sure your Laravel API is properly set up and configured to return meaningful error responses.

Set the API router -

Route::post('/register',  [AuthUserController::class, 'register']);

Set the Controller -

public function register(RegisterUserRequest $request) {
return response()->json(null, 200);
}

And Finally set The Request

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class RegisterUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}

/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{

return [
'name' => 'required|string|min:3|max:32',
'email' => 'required|email|unique:users,email',
'password' => 'required|string|min:6|max:8',
'password_confirmation' => 'required|string|same:password',
];

}
}

Step 2 — Start a React Project

npx create-react-app {name_of_app}
Install dependecies
npm install @reduxjs/toolkit
npm install react-redux
npm i react-router-dom@6
npm install axios
npm install sweetalert2

Create the Script

import './App.css';
import React, {useState} from 'react'
import {useDispatch} from "react-redux"
import Errors from "./hooks/ErrorInput"
import Swal from "sweetalert2";
import { store } from "./store/modules/registerUserSlice.js"
import SubmitBtn from "./components/SubmitBtn"
function App() {
// const [ErrorsData, setErrorList] = useState({})
const ErrorsData = new Errors({})
const [errorList, setErrorList] = useState({})
const dispatch = useDispatch()
const [data, setData] = useState({
procesing: false,
name: '',
email: '',
password: '',
password_confirmation: ''
})

const nameChangeHandler = (event) => {
setData ((prevState) =>
{ return { ...prevState, name: event.target.value}
})
}

const emailChangeHandler = (event) => {
setData ((prevState) =>
{ return { ...prevState, name: event.target.email}
})
}

const passwordChangeHandler = (event) => {
setData ((prevState) =>
{ return { ...prevState, name: event.target.password}
})
}

const passwordConfirmationChangeHandler = (event) => {
setData ((prevState) =>
{ return { ...prevState, name: event.target.password_confirmation}
})
}
const submitHandler = event => {
event.preventDefault();
setData ((prevState) =>
{ return { ...prevState, procesing: true }
})

// filters
const wait = setTimeout(() => {
clearTimeout(wait)
// DISPATCH
dispatch(store(data)).then((res) => {
// SUCCESS MESSAGE
Swal.fire({
title: 'Success !!',
text: `The User Was Registered`,
icon: 'success',
showConfirmButton: false,
timer: 1500
})
}).catch(error => {
// RESET ERROR LIST
// SET EERROR REQUEST
// ErrorsData.record(error.data.errors, 'form')
ErrorsData.record(error.data.errors, 'form')
Object.keys(error.data.errors).forEach(function(key) {
if(ErrorsData.has('form.' + key)){
setErrorList ((prevState) =>
{ return { ...prevState, [key]: ErrorsData.get('form.' + key)}
})
}
});
}).then(() => {
// LOADING FALSE
setData ((prevState) =>
{ return { ...prevState, procesing: false }
})
})
})

}

return (
<div className="App">
<div className="container">
<form method="POST" onSubmit={submitHandler}>
<fieldset>
<div className="field is-horizontal">
<div className="field-label">
<label className="label">Name</label>
</div>
<div className="field-body">
<div className="field">
<div className="control">
<input className="form-control ipone" name="name" type="text" onChange={nameChangeHandler} />
{errorList.name && <p className="error-msg">{errorList.name}</p>}
</div>
</div>
</div>
</div>
<div className="field is-horizontal">
<div className="field-label">
<label className="label">E-mail</label>
</div>
<div className="field-body">
<div className="field">
<div className="control">
<input className="form-control ipone" name="email" type="text" onChange={emailChangeHandler} />
{errorList.email && <p className="error-msg">{errorList.email}</p>}
</div>
</div>
</div>
</div>
<div className="field is-horizontal">
<div className="field-label">
<label className="label">Password</label>
</div>
<div className="field-body">
<div className="field">
<div className="control">
<input className="form-control ipone" name="password" type="text" onChange={passwordChangeHandler} />
{errorList.password && <p className="error-msg">{errorList.password}</p>}
</div>
</div>
</div>
</div>
<div className="field is-horizontal">
<div className="field-label">
<label className="label">Password Confirmation</label>
</div>
<div className="field-body">
<div className="field">
<div className="control">
<input className="form-control ipone" name="password_confirmation" type="text" onChange={passwordConfirmationChangeHandler} />
{errorList.password_confirmation && <p className="error-msg">{errorList.password_confirmation}</p>}
</div>
</div>
</div>
</div>
<div className="field is-horizontal">
<div className="field-label is-small">
</div>
<div className="field-body">
<div className="field">
<div className="control">
<SubmitBtn
processloading={data.procesing}
stylebutton="btn_cl_left btn-green-md1"
textbutton="Salvar"
/>
</div>
</div>
</div>
</div>
</fieldset>
</form>
</div>
</div>
);
}

Create The Store -
this will conect to the API that is in this url — http://localhost:8080/api/register
if does have erros , will return it to the script .

import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";
const initialState = [
{}
]
const pageInfo = {
STORE_URL: 'http://localhost:8080/api/register'
}

const registerUserSlice = createSlice({
name: 'registerUser',
initialState,
reducers: {

}
})

export const store = (data) => {
console.log('data', data)
return async () => {
const fetchData = async () => {
const response = await axios.post(pageInfo.STORE_URL, data)
return await response.data;
};

try {
return await fetchData();
} catch (error) {
throw error.response
}
}
}

export default registerUserSlice.reducer

And finaly our Error Class . that will handle the errors that comes from Laravel -

class Errors {
constructor () {
this.errors = {}
}

// GET ERRRO
get (field) {
// IF IS AN ARRAY ERROR
if (field.includes('.')) {
const splitString = field.split('.')

// IT IS AN ARRAY FORM -> FORM.IMAGE_GALLERY.0.IMAGES
if (splitString.length === 4) {
// LARAVEL FORM ERROR
const errorOne = this.errors[splitString[0]][splitString[1]][splitString[2] + '.' + splitString[3]]
if (errorOne) {
return errorOne
}

if (this.errors[splitString[0]][splitString[1]][splitString[2]][splitString[3]]) {
return this.errors[splitString[0]][splitString[1]][splitString[2]][splitString[3]][0]
}
}

// IT IS AN ARRAY FORM -> FORM.IMAGE_GALLERY.IMAGES
if (splitString.length === 3) {
// LARAVEL FORM ERROR
const errorOne = this.errors[splitString[0]][splitString[1] + '.' + splitString[2]]
if (errorOne) {
return errorOne
}

if (this.errors[splitString[0]][splitString[1]][splitString[2]]) {
return this.errors[splitString[0]][splitString[1]][splitString[2]][0]
}
}

// ADD ERROR INSIDE ARRAY LIST -> FORM.TITLE
if (this.errors[splitString[0]][splitString[1]]) {
return this.errors[splitString[0]][splitString[1]][0]
}

return false
}

// VALIDATE WITH NO FORM REFRENCE -> TITLE
if (this.errors[field]) {
return this.errors[field][0]
}
}

record (errors, list = null) {
this.clear()
console.log('vali salvarrrrrrrrrr')
if (list) {
this.errors = [list]
this.errors[list] = errors
return
}

this.errors = errors
}

// RESET RECORDS
reset () {
this.errors = {}
}

// ANY
any () {
return Object.keys(this.errors).length > 0
}

// HAS
has (field) {
// IF IS AN ARRAY ERROR
if (field.includes('.')) {
const splitString = field.split('.')

if ({}.hasOwnProperty.call(this.errors, splitString[0])) {
// IT IS AN ARRAY FORM -> FORM.IMAGE_GALLERY.0.IMAGES
if (splitString.length === 4) {
if ({}.hasOwnProperty.call(this.errors[splitString[0]][splitString[1]], splitString[2])) {
// LARAVEL FORM ERROR
const errorOne = this.errors[splitString[0]][splitString[1]][splitString[2] + '.' + splitString[3]]
if (errorOne) {
return errorOne
}

// MY FORM ERROR
return {}.hasOwnProperty.call(this.errors[splitString[0]][splitString[1]][splitString[2]], splitString[3])
}
return false
}

// IT IS AN ARRAY FORM -> FORM.IMAGE_GALLERY.IMAGES
if (splitString.length === 3) {
// LARAVEL FORM ERROR
const errorOne = this.errors[splitString[0]][splitString[1] + '.' + splitString[2]]
console.log('errorOne', errorOne)
if (errorOne) {
return errorOne
}
// MY FORM ERROR

// IT HAS FORM REFERENCE -> FORM.TITLE
const data = {}.hasOwnProperty.call(this.errors[splitString[0]], splitString[1])
if (!data) {
return false
}
return {}.hasOwnProperty.call(this.errors[splitString[0]][splitString[1]], splitString[2])
}

// IT HAS FORM REFERENCE -> FORM.TITLE
return {}.hasOwnProperty.call(this.errors[splitString[0]], splitString[1])
}

return false
}

// DOES NOT HAVA FORM REFERENCE -> TITLE
return {}.hasOwnProperty.call(this.errors, field)
}

// CLEAR
clear (field) {
if (field) delete this.errors[field]

this.errors = {}
}

// VERIFY ERROR AND CLEAR
verifyErrorAndClear (field) {
if (this.has(field)) {
this.clearField(field)
}
}

// CLEAR FIELD
clearField (field) {
// HAS ARRAY ERROR
if (field.includes('.')) {
const splitString = field.split('.')
const listName = splitString[0]
const fieldName = splitString[1]

delete this.errors[listName][fieldName]
return
}

delete this.errors[field]
}
}

export default Errors

Final Result

Conclusion

This tutorial provides a basic structure for error handling in a Vue.js application that communicates with a Laravel API. Customize it based on your specific API endpoints and error response structure.

The Git Hub Project -

Check as well how to use the same class, using vue js -

https://medium.com/@murilolivorato/handling-errors-with-vue-and-laravel-ea652603a495

Thanks a lot for reading till end. Follow or contact me via:
Github:https://github.com/murilolivorato
LinkedIn: https://www.linkedin.com/in/murilo-livorato-80985a4a/
Youtube : https://www.youtube.com/@murilolivorato1489

--

--