I'm using a form with JSON Form
and I'm having trouble performing validations. The problem is that the input fields turn red even before the user types anything. I want the error that the field is mandatory to appear only if the user clicks on the field, leaves it blank and clicks away again, or if the user tries to submit the form. Then it shows in red which fields are mandatory.
Can you tell me how to do this treatment correctly? Thank you
The form already has errors appearing when the page is first loaded:
'use client';
import { useState, useEffect } from 'react';
import { JsonForms } from '@jsonforms/react';
import { materialRenderers } from '@jsonforms/material-renderers';
import { materialCells } from '@jsonforms/material-renderers';
import axios from 'axios';
import { Button } from '@mui/material';
const schema = {
type: 'object',
properties: {
first_name: { type: 'string', title: 'First Name' },
last_name: { type: 'string', title: 'Last Name' },
email: { type: 'string', title: 'Email' },
linkedin_url: { type: 'string', title: 'LinkedIn URL' },
},
required: ['first_name', 'last_name', 'email', 'linkedin_url'],
};
const uischema = {
type: 'VerticalLayout',
elements: [
{ type: 'Control', scope: '#/properties/first_name' },
{ type: 'Control', scope: '#/properties/last_name' },
{ type: 'Control', scope: '#/properties/email' },
{ type: 'Control', scope: '#/properties/linkedin_url' },
],
};
export default function Home() {
const [submitted, setSubmitted] = useState(false);
const [formData, setFormData] = useState({});
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
const validateForm = () => {
const errors = {};
const requiredFields = ['first_name', 'last_name', 'email', 'linkedin_url'];
requiredFields.forEach((field) => {
if (!formData[field]) {
errors[field] = 'This field is required';
}
});
return Object.keys(errors).length === 0;
};
const onSubmit = async () => {
if (validateForm()) {
try {
await axios.post('/api/leads', formData);
setSubmitted(true);
} catch (error) {
console.error('Submission failed', error);
}
}
};
if (submitted) {
return <p>Thank you for submitting your information!</p>;
}
return (
<div
style={{
maxWidth: '500px',
margin: 'auto',
padding: '20px',
border: '1px solid #ccc',
borderRadius: '8px',
backgroundColor: '#f9f9f9',
}}
>
{isClient && (
<JsonForms
schema={schema}
uischema={uischema}
data={formData}
renderers={materialRenderers}
cells={materialCells}
onChange={({ data }) => setFormData(data)}
/>
)}
<Button
onClick={onSubmit}
variant="contained"
color="primary"
style={{ marginTop: '10px' }}
>
Submit
</Button>
</div>
);
}
Good question as I could not find sources online too, but I managed to bypass this issue with the below method.
In simplicity, the major idea is to manipulate the validationMode
with custom flags; other parts are not as important as they are and can be replaced.
// ...other imports and codes
export default function Home() {
// ...other useState()
const [isFirstTimeFormInitiated, setIsFirstTimeFormInitiated] =
useState(false);
const [isAccessed, setIsAccessed] = useState(false);
// ...other useEffect()
const validateForm = () => {
// remain unchanged
};
const onSubmit = async () => {
!isAccessed && setIsAccessed(true);
if (validateForm()) {
try {
await axios.post("/api/leads", formData);
setSubmitted(true);
} catch (error) {
console.error("Submission failed", error);
}
}
};
if (submitted) {
return <p>Thank you for submitting your information!</p>;
}
return (
<div
style={{
// ...other inline styling
}}
>
{isClient && (
<JsonForms
schema={schema}
uischema={uischema}
data={formData}
renderers={materialRenderers}
cells={materialCells}
onChange={(e) => {
setFormData(e.data);
if (!isFirstTimeFormInitiated) {
// REMARK: A rough way to prevent first time render
// triggering the setIsAccessed() too early.
setIsFirstTimeFormInitiated(true);
return;
}
!isAccessed && setIsAccessed(true);
}}
// REMARK: Manipulate this with custom flags to hide the errors.
validationMode={isAccessed ? "ValidateAndShow" : "ValidateAndHide"}
/>
)}
<Button
onClick={onSubmit}
variant="contained"
color="primary"
style={{ marginTop: "10px" }}
>
Submit
</Button>
</div>
);
}
You can check the full working example here: