请帮帮我!我对Redux的React是新的,我不知道我的代码有什么问题。一切正常,但每当我在家里点击类别中的不同选项卡时,它就会显示一个错误,因为状态发生了变化,我无法解决它。
这是错误:
“错误:不变量失败:在路径‘Category yPages.HOME.2.Products’中的调度之间检测到状态突变。”
如果在Switch用例中删除(案例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();
});
};
};
1条答案
按热度按时间wyyhbhjk1#
我没有深入挖掘,但我的解释是它不稳定,就像Brian说的
item["loaded"] = true
是一个突变,你不能在redux中突变状态,所以项应该是一种状态,你可以使用setState来修复(而不是就地突变),并使用setItemState(“已加载”)之类的东西来更新。