如何配置JSON Form仅在字段被触碰或提交表单时显示必填校验错误
如何配置JSON Form仅在字段被触碰或提交表单时显示必填校验错误
我来帮你搞定这个问题!你现在遇到的情况是JSON Forms默认会在页面加载时就根据Schema里的required规则执行校验,所以一打开页面所有必填字段就变红了。要实现只有用户触碰过字段后离开空值,或者点击提交按钮时才显示错误提示,我们可以通过追踪字段触碰状态、自定义校验逻辑来调整校验时机。
1. 新增状态追踪字段触碰和提交状态
我们需要几个额外的状态来控制校验逻辑:
touchedFields:记录哪些字段被用户点击过并离开(失去焦点)success:标记表单是否提交成功(替换原来的submitted,避免和提交校验状态混淆)submitted:标记是否触发过提交操作,用于触发全字段校验
const [touchedFields, setTouchedFields] = useState({}); const [success, setSuccess] = useState(false); const [submitted, setSubmitted] = useState(false); const [formData, setFormData] = useState({}); const [isClient, setIsClient] = useState(false);
2. 自定义校验逻辑,控制错误显示时机
我们需要覆盖JSON Forms的默认校验规则,只有当字段被触碰过,或者表单已提交时,才检查必填项并返回错误:
const customValidator = ({ data }) => { const errors = {}; const requiredFields = ['first_name', 'last_name', 'email', 'linkedin_url']; requiredFields.forEach(field => { // 只有满足「字段被触碰过」或「表单已提交」,且字段为空时,才返回错误 if ((touchedFields[field] || submitted) && !data[field]) { errors[field] = 'This field is required'; } }); return errors; };
3. 扩展渲染器,监听字段失去焦点事件
我们需要给每个表单控件添加onBlur事件,当用户离开字段时,标记该字段为已触碰:
// 扩展Material渲染器,给Control组件添加失去焦点监听 const customRenderers = materialRenderers.map(renderer => { // 只处理Control类型的渲染器 if (renderer.tester(1, { type: 'Control' })) { return { ...renderer, renderer: ({ controlProps, ...props }) => { const handleBlur = () => { // 从scope中提取字段名,比如#/properties/first_name → first_name const fieldName = props.scope.split('/').pop(); setTouchedFields(prev => ({ ...prev, [fieldName]: true })); }; return renderer.renderer({ ...props, controlProps: { ...controlProps, onBlur: handleBlur } }); } }; } return renderer; });
4. 修改提交逻辑,触发全字段校验
点击提交按钮时,先标记表单为已提交(触发所有必填字段的校验),再检查是否有错误,无错误则提交数据:
const onSubmit = async () => { // 标记表单已提交,触发所有必填字段的校验 setSubmitted(true); // 获取当前校验结果 const errors = customValidator({ data: formData }); if (Object.keys(errors).length === 0) { try { await axios.post('/api/leads', formData); setSuccess(true); } catch (error) { console.error('Submission failed', error); } } };
完整修改后的代码
'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 [touchedFields, setTouchedFields] = useState({}); const [success, setSuccess] = useState(false); const [submitted, setSubmitted] = useState(false); const [formData, setFormData] = useState({}); const [isClient, setIsClient] = useState(false); useEffect(() => { setIsClient(true); }, []); const customValidator = ({ data }) => { const errors = {}; const requiredFields = ['first_name', 'last_name', 'email', 'linkedin_url']; requiredFields.forEach(field => { if ((touchedFields[field] || submitted) && !data[field]) { errors[field] = 'This field is required'; } }); return errors; }; // 扩展Material渲染器,添加字段失去焦点监听 const customRenderers = materialRenderers.map(renderer => { if (renderer.tester(1, { type: 'Control' })) { return { ...renderer, renderer: ({ controlProps, ...props }) => { const handleBlur = () => { const fieldName = props.scope.split('/').pop(); setTouchedFields(prev => ({ ...prev, [fieldName]: true })); }; return renderer.renderer({ ...props, controlProps: { ...controlProps, onBlur: handleBlur } }); } }; } return renderer; }); const onSubmit = async () => { setSubmitted(true); const errors = customValidator({ data: formData }); if (Object.keys(errors).length === 0) { try { await axios.post('/api/leads', formData); setSuccess(true); } catch (error) { console.error('Submission failed', error); } } }; if (success) { 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={customRenderers} cells={materialCells} onChange={({ data }) => setFormData(data)} validator={customValidator} /> )} <Button onClick={onSubmit} variant="contained" color="primary" style={{ marginTop: '10px' }} > Submit </Button> </div> ); }
效果说明
- 页面加载时,所有字段都是正常状态,不会显示红色错误
- 当用户点击某个字段,输入后删除内容再离开(失去焦点),该字段会显示必填错误
- 当用户直接点击提交按钮,所有空的必填字段都会显示错误
- 提交成功后,显示感谢信息
备注:内容来源于stack exchange,提问作者vbernal
相关产品推荐
相关产品推荐

