React路由切换后子组件状态未更新,输入值缓存问题求助
问题场景
我用同一个TestSection组件处理/test/new和/test/edit/:id两个路由,目的是避免代码重复。但从/test/edit/:id路由点击按钮执行history.push("/test/new")跳转后,输入框里的值会被缓存,无法自动清空。
路由配置
<Route exact path={`/test/edit/:id`} component={TestSection}></Route> <Route exact path={`/test/new`} component={TestSection}></Route>
父组件代码(简化版)
import React, { useEffect, useState } from "react"; import { useHistory, useParams } from "react-router"; import { UseGet, UseWeb } from "../../hooks/http"; import TestInput from "./TestInput"; import Preloader from "../../components/preloader/Preloader"; const TestSection = (props) => { const { id } = useParams(); const history = useHistory(); const [errors, setErrors] = useState(); const [category, setCategory] = useState(); const [IsLoading, setIsLoading] = useState(true); const fetchCategory = async (id) => { const response = await UseGet(UseWeb("/categories/get"), { data: { term: "_id", value: id } } ); setCategory(response.outputData[0]) setIsLoading(false) }; const handleNew = () => { setCategory(undefined) history.push('/test/new') }; useEffect(() => { if (id) { fetchCategory(id).then((category) => { setCategory(category); }); } else { setIsLoading(false); } }, []); return ( <div className="container" style={{ maxWidth: "600px", padding: "90px" }}> <Preloader loading={IsLoading} /> {!IsLoading && <React.Fragment> <TestInput label="Title" name="title" value={category?.title} error={errors?.title} /> <button className="btn btn-primary" onClick={handleNew}>Add new</button> </React.Fragment> } </div> ); }; export default TestSection;
子组件代码(简化版)
import React, { useEffect, useState } from "react"; import { FormGroup, Label } from "reactstrap"; const TestInput = (props) => { const { value, name, error, copy, size, className, label, counter, onChange, ...rest } = props; const [inputValue, setInputValue] = useState(value); const handleChange = (ev) => { setInputValue(typeof onChange === "function" ? onChange(ev) : ev.target.value) }; console.log(value) useEffect(() => { }, []) return ( <FormGroup className="mb-2"> {label && <Label className="form-label fs-13px" htmlFor={name}> {label} {error && <span className="field-error">{error}</span>} </Label>} <div className={`form-control-wrap`}> <input type="text" id={name} value={inputValue} name={name} className={`form-control form-control-${size ? size : "sm"} ${className}`} onChange={handleChange} {...rest} /> </div> </FormGroup> ); }; export default TestInput;
解决方案
1. 父组件监听路由参数变化,重置状态
原代码中useEffect的依赖为空数组,仅在组件首次挂载时执行。当路由从/test/edit/:id切换到/test/new时,组件不会重新挂载,useEffect不会触发,导致category状态没有被重置。
修改父组件的useEffect,将id加入依赖数组,同时清理冗余的setCategory调用:
useEffect(() => { if (id) { setIsLoading(true); fetchCategory(id); } else { setCategory(undefined); // 清空category,让输入框值重置 setIsLoading(false); } }, [id]); // 监听id变化,路由切换时自动执行
同时修改fetchCategory里的冗余代码(原代码中fetchCategory已经调用了setCategory,不需要在then里重复设置):
const fetchCategory = async (id) => { const response = await UseGet(UseWeb("/categories/get"), { data: { term: "_id", value: id } } ); setCategory(response.outputData[0]); setIsLoading(false); };
2. 子组件同步props的value变化
子组件中用useState初始化了inputValue,但后续props.value变化时(比如父组件category变为undefined),inputValue没有同步更新,导致输入框显示旧值。
给子组件添加useEffect监听props.value,同步更新inputValue:
useEffect(() => { setInputValue(value); }, [value]); // 当props的value变化时,更新输入框的值
3. 优化跳转逻辑(可选)
原handleNew中先设置category再跳转,由于React状态更新是异步的,可能出现跳转后状态还未更新的情况。现在依赖useEffect监听id变化来重置状态,所以可以简化handleNew:
const handleNew = () => { history.push('/test/new'); };
完成以上修改后,从/test/edit/:id跳转到/test/new时,输入框的值会自动清空,不再缓存旧内容。
内容的提问来源于stack exchange,提问作者Infobuscador

