React+TypeScript中用Redux Toolkit单Reducer更新表单多字段问题
问题描述
开发包含name、image、price、qty、info五个字段的更新表单时,未使用useState维护本地状态,直接从Redux Store取表单数据并尝试更新Store后提交。但调用updateProductState reducer更新字段时,Store数据未正确更新,反而被设置成类似"name: name"的错误值。
ProductSlice.ts
export interface ProductState { loading: boolean; products: ProductModel[]; selectedProduct: ProductModel; error: string; } export let initialState: ProductState = { loading: false, products: [] as ProductModel[], selectedProduct: {} as ProductModel, error: '' }; export const productSlice = createSlice( { name: 'product', initialState, reducers: { updateProductState: ( state, action ) => { const { id, name, image, price, qty, info } = action.payload; return { ...state, selectedProduct: { id, name, image, price, qty, info, } as ProductModel, }; } } });
UpdateProduct.ts
import React, { useEffect, useState } from 'react'; import { useDispatch,useSelector } from 'react-redux'; import { useParams} from 'react-router-dom'; import { AppDispatch } from '../../../../redux/store'; import * as productActions from '../../../../redux/productsSlice'; import { useNavigate } from 'react-router'; interface IProps{ } interface IState { bigBasket: productActions.ProductState; } type URLParams= { productId: string; } const UpdateProduct: React.FC<IProps> = ({}) => { let dispatch = useDispatch<AppDispatch>(); const { productId } = useParams<URLParams>(); const navigate = useNavigate() let productState: productActions.ProductState = useSelector((store: IState) => store.bigBasket) let { error, loading, selectedProduct } = productState; useEffect( () => { dispatch(productActions.getSingleProductThunk(productId as string)); }, [productId]) let updateInput = (event:React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { dispatch(productActions.updateProductState({ name: event.target.name, value:event.target.value })) } let updateImage = async (event:React.ChangeEvent<HTMLInputElement | any>) => { let imageFile:Blob = event.target.files[0]; let base64Image:string | ArrayBuffer = await convertBase64String(imageFile); dispatch(productActions.updateProductState({ name: "image", value: base64Image })); }; let convertBase64String = (imageFile:Blob):Promise<string | ArrayBuffer> => { return new Promise((resolve, reject) => { let fileReader = new FileReader(); fileReader.readAsDataURL(imageFile); fileReader.addEventListener('load', () => { if (fileReader.result) { resolve(fileReader.result); } else { reject('Error Occurred'); } }) }); }; let submitUpdateProduct = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); dispatch(productActions.updateProductThunk({ product: selectedProduct, productId: productId as string, navigate })); } return ( <React.Fragment> {<pre>{JSON.stringify(selectedProduct)}</pre>} <section className='mt-3'> <div className="container"> <div className="row"> <div className="col-md-4"> <div className="card"> <div className="card-header bg-secondary bg-white"> <p className="h4 text-white">Update Product</p> </div> <div className="card-body rgba-green-light"> <form onSubmit={submitUpdateProduct}> <div className="mb-3"> <input required name="name" value={selectedProduct.name} onChange={updateInput} type="text" className="form-control" placeholder="Name" /> </div> <div className="mb-3"> <input onChange={updateImage} className="form-control" type="file" id="formFile" /> {selectedProduct.image && <img src={selectedProduct.image} alt="" width="25" height="25" /> } </div> <div className="mb-3"> <input required type="number" name="price" value={selectedProduct.price} onChange={updateInput} className='form-control' placeholder="Price" /> </div> <div className="mb-3"> <input required type="number" name="qty" value={selectedProduct.qty} onChange={updateInput} className='form-control' placeholder='Available Quantity' /> </div> <div className="mb-3"> <textarea required name="info" value={selectedProduct.info} onChange={updateInput} rows={3} className='form-control' placeholder="General Info" /> </div> <div className=""> <input type="submit" className="btn btn-secondary btn-sm" value="Update" /> </div> </form> </div> </div> </div> </div> </div> </section> </React.Fragment> ) } export default UpdateProduct
解决方案
问题根源是reducer接收的payload格式和组件dispatch的payload不匹配,且更新时未保留selectedProduct原有字段,导致每次更新都会丢失其他字段数据。
1. 修正reducer逻辑
当前updateProductState reducer期望payload包含id、name、image等所有字段,但组件实际传递的是{name: 字段名, value: 字段值}格式,需改成动态更新单个字段并保留原有数据:
updateProductState: (state, action) => { const { field, value } = action.payload; return { ...state, selectedProduct: { ...state.selectedProduct, // 保留原有所有字段 [field]: value // 动态更新目标字段 } as ProductModel }; }
2. 修正组件dispatch的参数
组件调用updateProductState时,需将字段名称用field传递,替换原来的name:
- 修改
updateInput函数:
let updateInput = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { dispatch(productActions.updateProductState({ field: event.target.name, value: event.target.value })) }
- 修改
updateImage函数:
dispatch(productActions.updateProductState({ field: "image", value: base64Image }));
3. 额外提醒
- 确保
getSingleProductThunk能正确获取单条产品数据并更新selectedProduct,否则初始空对象会导致表单显示异常; - 若
ProductModel包含id字段,更新过程中要保证selectedProduct始终保留id,避免提交时丢失主键信息。
内容的提问来源于stack exchange,提问作者Newbie2015
相关产品推荐
相关产品推荐

