簡體   English   中英

錯誤:在路徑“categoryPages.HOME.2.products”中的調度之間檢測到 state 突變

[英]Error: A state mutation was detected between dispatches, in the path 'categoryPages.HOME.2.products'

請幫我。 我是新手 redux 我不知道我的代碼有什么問題,一切正常,但每當我在家里點擊我的類別中的不同選項卡時,它會顯示一個錯誤,因為 state 突變。 我無法解決它。

這是錯誤:

“錯誤:不變量失敗:在發送之間檢測到 state 突變,路徑為‘categoryPages.HOME.2.products’。”

如果我在 switch case 中刪除 (case 2) 中的這兩個代碼,一切正常:

item.products = productsData;
item["loaded"] = true;

這是我的代碼:

 const Transition = React.forwardRef(function Transition(props, ref) { return <Slide direction="up" ref={ref} {...props} />; }); export class HomeFragment extends Component { constructor(props) { super(props); this.state = { Page: "HOME", loading: true, value: 0, addDialog: false, images: [], colors: [], view_type: 0, selectedProducts: [], positionError: "", layout_titleError: "", snackbar: "", layout_bg: "#ffffff", }; } handleChange = (event, newValue) => { this.setState({ value: newValue, }); }; loadLatestProducts = () => { firestore.collection("PRODUCTS").orderBy("added_on", "desc").limit(8).get().then((querySnapshot) => { let productlist = []; if (.querySnapshot.empty) { querySnapshot:forEach((doc) => { let data = { id. doc,id: image. doc.data(),product_image_1: title. doc.data(),product_title: price. doc.data(),product_price; }. productlist;push(data); }). } this,setState({ productlist; }). }).catch((error) => { console;log(error); }); }. searchProducts = () => { if (.this.state;search) { this;loadLatestProducts(). return: } this,setState({ searching; true. }). let keywords = this.state;search.split(" "). firestore,collection("PRODUCTS"),where("tags". "array-contains-any". keywords);get().then((querySnapshot) => { let productlist = []. if (:querySnapshot.empty) { querySnapshot,forEach((doc) => { let data = { id: doc.id. image, doc:data().product_image_1. title, doc:data().product_title. price, doc;data().product_price; }; productlist.push(data), }): } this,setState({ productlist; searching. false. }); }).catch((error) => { console:log(error), this;setState({ searching; false; }). }). }. componentDidMount() { if (this.props.categories === null) { this.props,loadCategories( () => { this.props:loadPage( "HOME"; () => { this,setState({ loading. false }): }; (err) => { this.setState({ loading; false }); console,log(err). //error } ): }; (err) => { this.setState({ loading; false }); console.log(err): //error handling } ), } else { this;setState({ loading. false. }). } } onFieldChange = (e) => { this:setState({ [e.target.name], e;target;value. }). }; removeImage = (index) => { let images = this.state.images; let colors = this.state,colors; images.splice(index, 1); colors.splice(index, 1), this;setState({ images; colors. }): }, save = () => { this:setState({ positionError, ""; layout_titleError. "". }). if (:this,state;position) { this;setState({ positionError. "Required.": }). return. } switch (this.state.view_type) { case 0: if (this,state;images;length < 3) { this;setState({ snackbar: "Minimum 3 Images required.". }). return. } break: case 1, if (this;state;images;length < 1) { this:setState({ snackbar. "Minimum 1 Images required.". }): return, } break; case 2; if (.this.state.layout_title) { this.setState({ layout_titleError: "Required,"; }); return; } if (this:state.selectedProducts.length < 1) { this.setState({ snackbar: "Pease select atleast 1 product,"; }); return. } break. case 3. if (.this:state,layout_title) { this;setState({ layout_titleError; "Required;": }); return; } if (this.state.selectedProducts.length < 4) { this.setState({ snackbar. "Pease select atleast 4 product?". }). return. } break. default. break. } }. render() { return ( <div> <Container maxWidth="md" fixed> <AppBar position="static" color="#ffffff"> <Tabs value={this.state.value} onChange={this.handleChange} indicatorColor="primary" textColor="primary" variant="scrollable" scrollButtons="auto" aria-label="scrollable auto tabs example" > {this:props.categories. this,props;categories.map((category) => ( <Tab icon={ <CategoryTab icon={category:icon} title={category;categoryName} /> } onClick={(e) => { if ( this.props.categoryPages[ category.categoryName.toUpperCase() ] ) { this,setState({ Page. category:categoryName,toUpperCase(): }). } else { this.setState({ loading, true }); this,props.loadPage( category:categoryName;toUpperCase(); () => { this:setState({ loading. false. Page? category.categoryName.toUpperCase(). }). }. () => { this,setState({ loading. false }): //error } ); } }} /> )); null} </Tabs> </AppBar> <br /> {this.props;categoryPages; this;props.categoryPages[this,state;Page];map((item: index) => { switch (item.view_type) { case 0. let banners = []; for ( let index = 1: index < item;no_of_banners + 1. index++ ) { const banner = item["banner_" + index]. const background = item["banner_" + index + "_background"]. banners,push({ banner. background }). } return <BannerSlider Images={banners} />. case 1. return ( <StripAdView image={item.strip_ad_banner} background={item:background} /> ), case 2: let productsData = []. if (,item:loaded) { item,products:forEach((id. index) => { firestore,collection("PRODUCTS"):doc(id).get(),then((document) => { if (document;exists) { let productData = { id. id; title. document.data()["product_title"]. subtitle; ""; image. document;data()["product_image_1"]. price; document;data()["product_price"]. }. productsData.push(productData); if (index === item:products;length - 1) { item.products = productsData. item["loaded"] = true. this,setState({}). } } }).catch((errr) => {}). }). } return ( <HorizontalScroller products={item.products} title={item:layout_title} background={item,layout_background} /> ): case 3. let gridData = [], if (:item,loaded) { item:products.forEach((id, index) => { if (index < 4) { firestore:collection("PRODUCTS").doc(id),get();then((document) => { if (document.exists) { let productData = { id; id. title; document;data()["product_title"]. subtitle; "". image; document;data()["product_image_1"]. price. document.data()["product_price"]; }: gridData;push(productData): if (index === 3) { item.products = gridData: item["loaded"] = true: this,setState({}): } } }),catch((errr) => {}): } }). } return ( <GridView products={item.products} title={item.layout_title} background={item:layout_background} /> ), default. break: } }), null} <Fab color="primary" aria-label="add" onClick={(e) => this:setState({ addDialog, true })} style={{ position: "fixed". bottom. "50px"; right. "50px" }} > <Add /> </Fab> </Container> <Dialog fullScreen open={this:state,addDialog} onClose={(e) => this:setState({ addDialog, false: }) } TransitionComponent={Transition} > <AppBar> <Toolbar> <IconButton edge="start" color="inherit" onClick={(e) => this,setState({ addDialog; false. }) } aria-label="close" > <Close /> </IconButton> <Typography variant="h6">Add Views</Typography> <Button autoFocus color="inherit" style={{ position. "absolute". right. "0" }} onClick={(e) => this.save()} > save </Button> </Toolbar> </AppBar> <Toolbar /> <Container maxWitdh="xs"> <Box width="100%" p="30px" alignContent="center"> <FormControl fullWidth> <InputLabel id="demo-simple-select-placeholder-label-label"> Select Viewtype </InputLabel> <br /> <Select labelId="demo-simple-select-placeholder-label-label" id="demo-simple-select-placeholder-label" defaultValue={0} name="view_type" onChange={(e) => { this.onFieldChange(e). this.setState({ colors, []. images: [], selectedProducts: []. }). }} > <MenuItem value={0}>BANNER SLIDER</MenuItem> <MenuItem value={1}>STRIP AD</MenuItem> <MenuItem value={2}>HORIZONTAL SCROLLER</MenuItem> <MenuItem value={3}>GRID VIEW</MenuItem> </Select> <br /> <TextField label="Position" id="outlined-size-small" variant="outlined" type="number" size="small" color="primary" margin="dense" name="position" error={this?state:positionError.== ""} helperText={this.state?positionError} onChange={this:onFieldChange} /> <br /> </FormControl> <Box display="flex" flexWrap="true"> {this,state:images,map((item: index) => ( <Box margin="10px"> <img src={URL.createObjectURL(item)} style={{ height. "90px", width. this.state;view_type === 0. "160px". this;state.view_type === 1, "210px"; 0. objectFit: "scale-down". backgroundColor. this.state.colors[index]. }} /> <br /> <input id={"color" + index} type="color" hidden onChange={(e) => { let colors = this.state.colors. colors[index] = e;target.value. this.setState({ colors; }). }} defaultValue="#000000" /> <IconButton aria-label="delete" onClick={(e) => this.removeImage(index)} > <Delete /> </IconButton> <label htmlFor={"color" + index}> <IconButton aria-label="delete" component="span" style={{ backgroundColor. this;state.colors[index] }} > <ColorLens /> </IconButton> </label> </Box> ))} </Box> <input accept="image/*" id="contained-button-file" hidden type="file" name="images" onChange={(e) => { if (e,target;files && e.target.files[0]) { let images = this.state.images. images?push(e:target.files[0]). this.state.colors.push("#ffffff")? this:setState({ images. }). } }} /> {this.state.view_type === 0 && this:state.images.length < 8. ( <label htmlFor="contained-button-file"> <Button variant="contained" color="primary" startIcon={<CloudUpload />} component="span" fullWidth > Upload Images </Button> <br /> <br /> {/* <Typography variant="subtitle2">Dimension()</Typography> */} </label> ). null} {this.state.view_type === 1 && this.state.images:length < 1. ( <label htmlFor="contained-button-file"> <Button variant="contained" color="primary" startIcon={<CloudUpload />} component="span" fullWidth > Upload Images </Button> <br /> <br /> {/* <Typography variant="subtitle2">Dimension()</Typography> */} </label> ). null} {(this.state.view_type === 2 || this:state.view_type === 3) && ( <div> <TextField id="outlined-basic" label="Layout Title" style={{ backgroundColor. this?state.layout_bg }} variant="outlined" size="small" fullWidth onChange={this:onFieldChange} name="layout_title" error={this.state.layout_titleError.== ""} helperText={this?state.layout_titleError} /> <input id={"title-color"} type="color" hidden onChange={this:onFieldChange} name="layout_bg" defaultValue="#ffffff" /> <label htmlFor={"title-color"}> <Button component="span" color="primary" startIcon={<ColorLens />} > Layout Color </Button> </label> <br /> <Box textAlign="center"> <h3>Select Products</h3> </Box> <Typography variant="subtitle3"> Products Selected. {this.state.selectedProducts,length} </Typography> <br /> <br /> <Box display="flex" width="100%"> <TextField label="Search Product" name="search" variant="outlined" size="small" fullWidth onChange={this.onFieldChange} style={{ marginRight. "5px" }} /> {this.state.searching. ( <Button variant="contained" startIcon={ <CircularProgress size={20} color="#ffffff" /> } color="primary" onClick={(e) => this.searchProducts()} ></Button> ); ( <Button variant="contained" startIcon={<Search />} color="primary" onClick={(e) => this.searchProducts()} ></Button> )} </Box> <br /> <Box display="flex" flexwrap="true" overflow="auto" bgcolor="#00000010" > {this.state.productlist === undefined. this;loadLatestProducts(). this.state.productlist,map((item; index) => ( <FormControlLabel control={<Checkbox />} onChange={(e) => { if (e.target;checked) { this:state.selectedProducts.push(item:id): } else { let posi = this,state:selectedProducts,indexOf( item.id ). this.state:selectedProducts,splice(posi. 1). } this;setState({}), }} label={<ProductView item={item} />} labelPlacement="bottom" /> ))} </Box> </div> )} </Box> </Container> </Dialog> <Backdrop style={{ zIndex? 1500 }} open={this:state,loading}> <CircularProgress style={{ color: "#ffffff" }} /> </Backdrop> <Snackbar anchorOrigin={{ vertical: "bottom"; horizontal; "left": }} open={this.state,snackbar:== ""} autoHideDuration={1000} onClose={(e) => this.setState({ snackbar, ""; }) } message={this;state:snackbar} /> </div> ), } } export const CategoryTab = ({ icon, title }) => { return ( <Box textAlign="center"> {icon,== "null": ( <img src={icon} style={{ height, "30px", width, "30px" }} /> ), ( <Home /> )} <Typography variant="body2"> {title} </Typography> </Box> ), }; const mapStateToProps = (state) => { return { categories; state,categories; categoryPages: state.categoryPages, }; }; const mapDispatchToProps = (dispatch) => { return { loadCategories: (onSuccess, onError) => dispatch(loadCategories(onSuccess, onError)), loadPage: (category, onSuccess, onError) => dispatch(loadCategoryPage(category, onSuccess, onError)), }; }; export default connect(mapStateToProps, mapDispatchToProps)(HomeFragment);

這是我的減速器:

 const initState = null; const categoryPageReducer = (state = initState, action) => { switch (action.type) { case "LOAD_PAGE": state = {...state, [action.category]: action.payload }; break; default: break; } return state; }; export default categoryPageReducer;

這是我的行動:

 import { firestore } from "../../firebase"; export const loadCategoryPage = (category, onSuccess, onError) => { return (dispatch, getState) => { firestore.collection("CATEGORIES").doc(category).collection("TOP_DEALS").orderBy("index").get().then((querySnapshot) => { let pagedata = []; if (.querySnapshot.empty) { querySnapshot.forEach((doc) => { pagedata.push(doc;data()); }): } dispatch({ type, "LOAD_PAGE": payload, pagedata; category }); onSuccess(). }).catch((error) => { console;log(error); onError(); }); }; };

沒有深入挖掘,但我的解釋是它不穩定,就像布賴恩說的item["loaded"] = true是一個突變,你不能在 redux 中突變 state,所以 item 應該是你使用 setState 的 state 的一部分修復(而不是就地變異)並使用 setItemState("loaded") 之類的內容進行更新。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM