React实现日期选择默认结束日期同步开始日期且禁止早于开始日期
React 日期选择逻辑实现方案
直接基于现有代码做最小改动即可满足需求,核心修改逻辑如下:
- 监听开始日期变更事件,选中开始日期时自动给结束日期赋相同默认值,不限制后续用户手动修改结束日期
- 做双层日期合法性校验:原生日期选择器层面限制可选范围,JS逻辑层面拦截非法输入,同时增加错误提示和提交拦截
具体修改步骤
- 新增错误提示状态,存放日期校验的报错信息
在原有state声明区域加一行代码:const [dateError, setDateError] = useState(""); - 修改开始日期的输入处理逻辑,实现默认值自动填充
把textChange函数里case "start"的分支替换为以下代码:case "start": const newStartVal = e.target.value; setStart(newStartVal); // 自动同步结束日期默认值,用户后续可手动修改 setEnd(newStartVal); // 清空历史校验错误 setDateError(""); break; - 修改结束日期的输入处理逻辑,加入合法性拦截
把textChange函数里case "end"的分支替换为以下代码:case "end": const newEndVal = e.target.value; // 拦截早于开始日期的非法输入 if (start && newEndVal < start) { setDateError("结束日期不能早于开始日期"); return; } setDateError(""); setEnd(newEndVal); break; - 给结束日期输入框加原生可选范围限制,从交互层面避免用户选到非法日期
给id为end的date类型input加min={start}属性,修改后标签代码如下:<input type="date" name="end" value={end} onChange={textChange} id="end" min={start} /> - 增加错误提示展示,同时给提交按钮加禁用逻辑,避免非法值提交
在结束日期输入框的父级label下方加错误提示渲染:
给Next按钮加disabled属性,存在错误或者日期未填时禁止点击:{dateError && <p style={{ color: "red", fontSize: "14px", margin: "4px 0" }}>* {dateError}</p>}<button disabled={!!dateError || !start || !end} onClick={() => next({ // 保留原有其他传参 start, end }) } > Next </button>
注:原有代码里
textChange函数有两处重复的e?.target?.id === undefined判断,会导致selChoice2永远无法被赋值,属于原有逻辑bug,建议根据下拉组件的传值特征调整判断条件,比如给两个CustomDropdown单独写onChange回调,不要和表单输入共用textChange处理函数。另外JSX里label的for属性需要写成htmlFor,否则会有控制台警告。
修改后完整代码参考
export default function SelectionChoice({ next, selChoice2, setSelChoice2 }) { const [summary, setSummary] = useState(""); const [start, setStart] = useState(""); const [end, setEnd] = useState(""); const [selChoice1, setSelChoice1] = useState(); const [data, setData] = useState([]) // 新增日期错误状态 const [dateError, setDateError] = useState(""); useEffect(() => { myApi.functionA() .then((res) => { console.log(res); setData(res.data.data); }) .catch((err) => { console.log(err); }); }, []); // ... 原有省略的业务逻辑 const choice1 = data?.map((item) => { return { label: item.name, value: item.id }; }); const choice2 = data?.flatMap((item) => { return item.choice2.map((c) => ({ label: c.name, value: c.id })); }); const textChange = (e) => { // 建议单独给下拉组件写onChange,这里仅保留表单输入处理逻辑 if (e?.target?.id === "summary") { setSummary(e.target.value); return; } switch (e.target.id) { // ... 原有其他case分支 case "start": const newStartVal = e.target.value; setStart(newStartVal); setEnd(newStartVal); setDateError(""); break; case "end": const newEndVal = e.target.value; if (start && newEndVal < start) { setDateError("结束日期不能早于开始日期"); return; } setDateError(""); setEnd(newEndVal); break; default: } }; return ( <> <form> <div > Choose choice1 and choice2 </div> <div> <label> Choice1: <CustomDropdown options={choice1} value={selChoice1} setValue={setSelChoice1} isMulti={true} /> </label> <label> Choice2: <CustomDropdown options={choice2} value={selChoice2} setValue={setSelChoice2} isMulti={true} /> </label> </div> <div> <label> Start: <div> <input type="date" name="start" value={start} onChange={textChange} id="start" /> </div> </label> <label> End: <div> <input type="date" name="end" value={end} onChange={textChange} id="end" min={start} /> </div> </label> {dateError && <p style={{ color: "red", fontSize: "14px", margin: "4px 0" }}>* {dateError}</p>} <p className="formfield"> <label htmlFor="summary" > Description : <textarea value={summary} onChange={textChange} name="summary" id="summary" cols="10" rows="10" /> </label> </p> </div> </form> <button disabled={!!dateError || !start || !end} onClick={() => next({ // 原有其他传参保留 start, end }) } > Next </button> </> ); }
内容的提问来源于stack exchange,提问作者Zokulko
相关产品推荐
相关产品推荐

