React.js中useCallback使用更新后state对象发起POST请求的问题
解决方案
你遇到的核心问题是React状态更新为异步批量执行,调用setQuoteDetails后当前闭包内的quoteDetails仍为旧值,无法立即拿到更新后的状态发起请求。最简洁的实现无需新增额外状态和监听useEffect,直接调整请求函数支持自定义入参即可:
核心改动点
- 修改
createQuote函数,新增可选的自定义payload参数,默认使用当前quoteDetails状态 - 无deal提交场景下,直接在点击事件内构造不包含deal的临时payload,传入请求函数即可,完全不修改原状态,也不需要等待状态更新
完整修改后代码
function QuotePreview(props) { const [quoteDetails, setQuoteDetails] = useState({}); const [loadingCreateQuote, setLoadingCreateQuote] = useState(false); useEffect(() => { if(apiResponse?.content?.quotePreview?.quoteDetails) { setQuoteDetails(apiResponse?.content?.quotePreview?.quoteDetails); } }, [apiResponse]); const onGridUpdate = (data) => { let subTotal = data.reduce((subTotal, {extendedPrice}) => subTotal + extendedPrice, 0); subTotal = Math.round((subTotal + Number.EPSILON) * 100) / 100 setQuoteDetails((previousQuoteDetails) => ({ ...previousQuoteDetails, subTotal: subTotal, Currency: currencySymbol, items: data, })); }; // 新增可选customPayload参数,默认使用当前quoteDetails const createQuote = async (customPayload = quoteDetails) => { try { setLoadingCreateQuote(true); // 请求使用传入的payload,没有传就用默认状态值 const result = await usPost(componentProp.quickQuoteEndpoint, customPayload); if (result.data?.content) { /** TODO: next steps with quoteId & confirmationId */ console.log(result.data.content); } return result.data; } catch( error ) { return error; } finally { setLoadingCreateQuote(false); } }; const handleQuickQuote = useCallback(createQuote, [quoteDetails, loadingCreateQuote]); const handleQuickQuoteWithoutDeals = (e) => { e.preventDefault(); // 解构去掉deal,构造临时payload,完全不修改原状态 const { deal, ...payloadWithoutDeal } = quoteDetails; // 直接传入处理好的payload发起请求 handleQuickQuote(payloadWithoutDeal); // 如果你同时需要更新页面状态,去掉deal展示,可以加下面这行,不影响请求逻辑 // setQuoteDetails(payloadWithoutDeal); } const generalInfoChange = (generalInformation) =>{ setQuoteDetails((previousQuoteDetails) => ( { ...previousQuoteDetails, tier: generalInformation.tier, } )); } const endUserInfoChange = (endUserlInformation) =>{ setQuoteDetails((previousQuoteDetails) => ( { ...previousQuoteDetails, endUser: endUserlInformation, } )); } return ( <div className="cmp-quote-preview"> {/* child components [handleQuickQuote will be passed down] */} </div> ); }
方案优势
- 完全遵循不可变原则,没有直接修改原状态对象,避免意外渲染异常
- 无需新增额外状态和useEffect监听,彻底避免初始化自动触发请求的问题
- 逻辑解耦,临时修改payload仅作用于本次请求,不会影响其他依赖
quoteDetails的业务逻辑 - 代码改动量极小,原有普通提交逻辑完全不需要调整
内容的提问来源于stack exchange,提问作者Thomas Sebastian
相关产品推荐
相关产品推荐

