React 在启动时不显示 UI 的初始状态

React doesn't display initial state of UI on start

我正在使用 React 构建一个电子商务应用程序,我偶然发现了一个问题,即 React 不会根据首次启动页面时的初始状态呈现 UI。

问题描述:

-> 是什么情况导致 React 无法根据初始状态渲染项目,我们该如何解决?

下面是我的代码:

ProductList.jsx

const ProductList = () => {
  const location = useLocation()
  const category = location.pathname.split("/")[2]
  const [filters, setFilters] = useState({});
  const [sort, setSort] = useState("latest");

  // For Filters Bar
  const handleFilters = (e) => {
    const value = e.target.value
    // When choosing default value, show all products:
    if (value === "") {
      return setFilters([])
    } else {
      setFilters({
        ...filters,
        [e.target.name]: value,
      })
    }
  }

  return (
    <Container>
      <Navbar />
      <Announcement />
      <Title>Dresses</Title>
      <FilterContainer>
        <Filter>
          <FilterText>Filter Products: </FilterText>
          <Select name="color" onChange={handleFilters}>
            <Option value="">All Color</Option>
            <Option value="white">White</Option>
            <Option value="black">Black</Option>
            <Option value="brown">Brown</Option>
            <Option value="red">Red</Option>
            <Option value="blue">Blue</Option>
            <Option value="yellow">Yellow</Option>
            <Option value="green">Green</Option>
          </Select>
          <Select name="size" onChange={handleFilters}>
            <Option value="">All Size</Option>
            <Option>XS</Option>
            <Option>S</Option>
            <Option>M</Option>
            <Option>L</Option>
            <Option>XL</Option>
            <Option>36</Option>
            <Option>37</Option>
            <Option>38</Option>
            <Option>39</Option>
            <Option>40</Option>
            <Option>41</Option>
            <Option>42</Option>
            <Option>43</Option>
          </Select>
        </Filter>
        <Filter>
          <FilterText>Sort Products: </FilterText>
          <Select onChange={e => setSort(e.target.value)}>
            <Option value="latest">Latest</Option>
            <Option value="oldest">Oldest</Option>
            <Option value="asc">Price ↑ (Low to High)</Option>
            <Option value="desc">Price ↓ (High to Low)</Option>
          </Select>
        </Filter>
      </FilterContainer>
      <Products category={category} filters={filters} sort={sort} />
      <Newsletter />
      <Footer />
    </Container>
  );
}

Products.jsx

const Products = ({ category, filters, sort }) => {

  const [products, setProducts] = useState([])
  const [filteredProducts, setFilteredProducts] = useState([])

  useEffect(() => {
    const getProducts = async () => {
      try {
        const res = await axios.get( category 
          ? `http://localhost:5000/api/products?category=${category}` 
          : `http://localhost:5000/api/products`
        )

        setProducts(res.data)
      } catch (err) {
        console.log(`Fetch all items failed - ${err}`)
      }
    }
    getProducts()
  }, [category])

  useEffect(() => {
    category && setFilteredProducts(
      products.filter(item => 
        Object.entries(filters).every(([key, value]) => 
          item[key].includes(value)
        )
      )
    )
  }, [category, filters, products])

  // Sorting:
  useEffect(() => {
    console.log(sort)
    if (sort === "latest") {
      setFilteredProducts(prev =>
        [...prev].sort((a, b) => b.createdAt.localeCompare(a.createdAt))
      )
    } else if (sort === "asc") {
      setFilteredProducts(prev =>
        [...prev].sort((a, b) => a.price - b.price)
      )
    } else if (sort === "desc") {
      setFilteredProducts(prev =>
        [...prev].sort((a, b) => b.price - a.price)
      )
    } else {
      setFilteredProducts(prev =>
        [...prev].sort((a, b) => a.createdAt.localeCompare(b.createdAt))
      )
    }
  }, [sort])

  return (
    <Container>
      <Title>Popular In Store</Title>
      <ProductsWrapper>
        {filteredProducts.map(item => (
        <Product key={item._id} item={item} />
      ))}
      </ProductsWrapper>
    </Container>
  );
}

API 路线 - product.js

const router = require('express').Router()
const { verifyTokenAndAdmin } = require('./verifyToken')
const Product = require('../models/Product')

// .... (Other CRUD)

// GET ALL PRODUCTS
router.get("/", async(req, res) => {
  const queryNew = req.query.new
  const queryCategory = req.query.category

  try {
    let products = []
    
    if(queryNew) {
      products = await Product.find().sort({ createdAt: -1 }).limit(5)
    } else if (queryCategory) {
      products = await Product.find({ 
        categories: {
          $in: [queryCategory],
        }, 
      })
    } else {
      products = await Product.find()
    }

    res.status(200).json(products)
  } catch(err) {
    res.status(500).json(`Cannot fetch all products - ${err}`)
  }
})

module.exports = router

演示:


更新

根据,我将过滤器的初始状态输入错误为数组。

代码

const ProductList = () => {
  const location = useLocation()
  const category = location.pathname.split("/")[2]
  const [filters, setFilters] = useState({});
  const [sort, setSort] = useState("latest");

  const handleFilters = (e) => {
    const value = e.target.value
    // When choosing default value, show all products:
    if (value === "") {
      setFilters({}) // Changed from array to object
    } else {
      setFilters({
        ...filters,
        [e.target.name]: value,
      })
    }
  }

...

 <Filter>
  <FilterText>Sort Products: </FilterText>
  <Select name="sort" onChange={e => setSort(e.target.value)} >
    <Option defaultValue="latest">Latest</Option>
    <Option value="oldest">Oldest</Option>
    <Option value="asc">Price ↑ (Low to High)</Option>
    <Option value="desc">Price ↓ (High to Low)</Option>
  </Select>
</Filter>

我遇到了来自 lama 的这段代码的问题,所以首先我看到一个错误,你将 useState 定义为 initialState 作为对象,并且在你的 handleFilters 中你使用了一个数组

const [filters, setFilters] = useState({}); // you use Object there
const handleFilters = (e) => {
const value = e.target.value
products:
if (value === "") {
  return setFilters([]) //. you should use object there not array.
} else {
  setFilters({
    ...filters,
    [e.target.name]: value,
  })
}}

所以在 React 中,当你使用 onChange 时,建议在你的 html 元素中使用一个值或 defaultValue 解决这个问题看看这个 CodeSandebox link: https://codesandbox.io/s/determined-hopper-gp9ym9?file=/src/App.js 让我知道它是否解决了您的问题。

如果你把日志放在你的 useEffect 钩子里,我假设执行顺序是:

[sort]->[category]->[category, filters, products]

[category]useEffect有一个ajax调用,肯定会在[sort]之后生效,当它生效时,会运行你的查询没有使用 req.query.new,所以它只会 运行 这个分支(我猜你确实有类别)

else if (queryCategory) {
    products = await Product.find({ 
        categories: {
          $in: [queryCategory],
        }, 
      })
    }

,它应该默认 return 升序列表只匹配 'oldest' 选项。

所以一般来说,您的排序效果在初始加载时根本不起作用,因为它总是会被其他效果覆盖。

因此,您要么使用默认降序条件进行所有查询以匹配默认 'latest' 选项,要么您可以在执行任何查询后触发排序,选项 2 看起来更好,但您需要考虑您的预期更改其他元素(过滤器、类别)时的排序行为。

您不应将更改后的 products 放入状态,因为这会使保持状态更新变得格外复杂,并且您需要处理各种 useEffect 情况。相反,最好定义排序和过滤函数并在渲染时应用它们。这样你就可以确保渲染结果总是 up-to-date 数据:

const filterProducts = (products) => {
  if (!category) {
    return products;
  }
  return products.filter(item =>
    Object.entries(filters).every(([key, value]) =>
      item[key].includes(value),
    ),
  );
};

const sortProducts = (products) => {
  switch (sort) {
    case "asc":
      return [...products].sort((a, b) => a.price - b.price);
    case "desc":
      return [...products].sort((a, b) => b.price - a.price);
    case "latest":
    default:
      return [...products].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
  }
};

return (
  <Container>
    <Title>Popular In Store</Title>
    <ProductsWrapper>
      {sortProducts(filterProducts(products)).map(item => (
        <Product key={item._id} item={item} />
      ))}
    </ProductsWrapper>
  </Container>
);