错误:在路径‘ategoryPages.HOME.2.Products’中的派单之间检测到状态突变

hrysbysz  于 2022-10-12  发布在  Go
关注(0)|答案(1)|浏览(92)

请帮帮我!我对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();
      });
  };
};
wyyhbhjk

wyyhbhjk1#

我没有深入挖掘,但我的解释是它不稳定,就像Brian说的item["loaded"] = true是一个突变,你不能在redux中突变状态,所以项应该是一种状态,你可以使用setState来修复(而不是就地突变),并使用setItemState(“已加载”)之类的东西来更新。

相关问题