React 在启动时不显示 UI 的初始状态
React doesn't display initial state of UI on start
我正在使用 React 构建一个电子商务应用程序,我偶然发现了一个问题,即 React 不会根据首次启动页面时的初始状态呈现 UI。
问题描述:
- 我有一个
sort
状态,其初始状态为 "latest"
,基于此排序功能 - 如果 sort
的值为 "latest"
- 它会对 return 最新的项目进行排序。
- 但是在开始或刷新页面时,排序的默认值和状态仍将是“最新”,但 UI 只会首先显示最旧的项目。
- 我必须单击其他选项,然后再次选择“最新”选项才能使排序逻辑通过。当我刷新页面时,问题又回来了。
- 排序其他值的逻辑工作正常。在下面的演示中,您可以看到我注销
sort
当前状态。开始时,sort
值已经是 "latest"
。
- 在 API
product.js
文件中,我已经使用 mongoose 按字段 createdAt - 1
对项目进行了排序,但它似乎不适用于 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
演示:
- 解释演示:开始时,它首先呈现最旧的项目。必须选择另一个选项,然后 return 到最新选项才能呈现。但是在控制台中,sort的初始状态已经是“latest”了,但是和useEffect的排序逻辑不匹配。
更新
根据,我将过滤器的初始状态输入错误为数组。
- 我已修复它并在排序 select 上添加了一个
name="sort"
。
- 我还用
defaultValue="latest"
替换了 value="latest"
我的 排序 select 栏 。 -> 这使得 最新选项 停止工作所以我认为它不能用于这种情况?
- 结果还是一样,UI 没有渲染排序栏的逻辑,先显示最新的项目。
代码
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>
);
我正在使用 React 构建一个电子商务应用程序,我偶然发现了一个问题,即 React 不会根据首次启动页面时的初始状态呈现 UI。
问题描述:
- 我有一个
sort
状态,其初始状态为"latest"
,基于此排序功能 - 如果sort
的值为"latest"
- 它会对 return 最新的项目进行排序。 - 但是在开始或刷新页面时,排序的默认值和状态仍将是“最新”,但 UI 只会首先显示最旧的项目。
- 我必须单击其他选项,然后再次选择“最新”选项才能使排序逻辑通过。当我刷新页面时,问题又回来了。
- 排序其他值的逻辑工作正常。在下面的演示中,您可以看到我注销
sort
当前状态。开始时,sort
值已经是"latest"
。 - 在 API
product.js
文件中,我已经使用 mongoose 按字段createdAt - 1
对项目进行了排序,但它似乎不适用于 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
演示:
- 解释演示:开始时,它首先呈现最旧的项目。必须选择另一个选项,然后 return 到最新选项才能呈现。但是在控制台中,sort的初始状态已经是“latest”了,但是和useEffect的排序逻辑不匹配。
更新
根据
- 我已修复它并在排序 select 上添加了一个
name="sort"
。 - 我还用
defaultValue="latest"
替换了value="latest"
我的 排序 select 栏 。 -> 这使得 最新选项 停止工作所以我认为它不能用于这种情况? - 结果还是一样,UI 没有渲染排序栏的逻辑,先显示最新的项目。
代码
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>
);