如何在 React 中即时显示组件的变化?
How can I display changes in component instantly in React?
我正在使用 MERN 堆栈构建此网站并遇到此呈现错误:
开始时,我有一个 foodList
table 渲染出数据库中的所有食物。
我已经有一个 useEffect()
和依赖项数组中的 foodList
- 所以用户随时更改 foodList
table ( Add/Edit/Delete), 它会在不刷新页面的情况下立即渲染出添加的菜肴。
当用户在此搜索和过滤栏中搜索内容时,它将隐藏 foodList
table 和 return 从 foodList
数组中筛选出的 searchedFood
的 table。
但是当用户使用这个搜索和过滤功能然后尝试编辑/从searchedFood
table中删除。它不会立即呈现更改 - 他们必须刷新页面才能看到所做的更改。
这可能与 useEffect()
有关,但我不知道如何在不中断 foodList
table 的情况下将其应用于 searchedFood
table .
App.js
export default function App() {
const [foodName, setFoodName] = useState('')
const [isVegetarian, setIsVegetarian] = useState('no')
const [priceRange, setPriceRange] = useState('$')
const [foodUrl, setFoodUrl] = useState('')
const [foodList, setFoodList] = useState([])
const [searchedFood, setSearchedFood] = useState([])
const [noResult, setNoResult] = useState(false)
// Display food list:
useEffect(() => {
let unmounted = false
Axios.get("https://project.herokuapp.com/read")
.then((response) => {
if (!unmounted) {
setFoodList(response.data)
}
})
.catch(error => {
console.log(`The error is: ${error}`)
return
})
return () => {
unmounted = true
}
}, [foodList])
// Add Food to list:
const addToList = async (event) => {//Axios.post logic in here}
// Paginate states:
const [currentPage, setCurrentPage] = useState(1)
const [foodPerPage] = useState(5)
// Get current food:
const indexOfLastFood = currentPage * foodPerPage
const indexOfFirstFood = indexOfLastFood - foodPerPage
const currentFood = foodList.slice(indexOfFirstFood, indexOfLastFood)
const currentSearchedFood = searchedFood.slice(indexOfFirstFood, indexOfLastFood)
const paginate = (pageNumber) => {
setCurrentPage(pageNumber)
}
return (
<section>
<FilterSearch
foodList={foodList}
searchedFood={searchedFood}
setSearchedFood={setSearchedFood}
noResult={noResult}
setNoResult={setNoResult}
paginate={paginate}
/>
{noResult ? <ResultNotFound/>
:
<FoodListTable
foodName={foodName}
priceRange={priceRange}
isVegetarian={isVegetarian}
foodUrl={foodUrl}
foodList={foodList}
currentFood={currentFood}
searchedFood={searchedFood}
currentSearchedFood={currentSearchedFood}
totalFood={foodList.length}
totalSearchedFood={searchedFood.length}
currentPage={currentPage}
paginate={paginate}
noResult={noResult}
foodPerPage={foodPerPage}
/>
}
</section>
)
}
FoodListTable.js
export default function FoodListTable(props) {
return (
<div>
<table>
<thead>
<tr>
<th>
Food name
</th>
<th>Price</th>
<th>
Action
</th>
</tr>
</thead>
<body>
// Return a table with data from searchFood on search:
{props.searchedFood.length > 0 ? props.currentSearchedFood.map((val) => {
return (
<FoodListRow
val={val}
key={val._id}
foodName={val.foodName}
isVegetarian={val.isVegetarian}
priceRange={val.priceRange}
foodUrl={val.foodUrl}
/>
)
}) : props.currentFood.map((val) => { // If not on search, return a table with data from foodList:
return (
<FoodListRow
val={val}
key={val._id}
foodName={val.foodName}
isVegetarian={val.isVegetarian}
priceRange={val.priceRange}
foodUrl={val.foodUrl}
/>
)
})
}
</tbody>
</table>
// Display different Pagination on searched table and food list table:
{props.searchedFood.length > 0 ?
<Pagination foodPerPage={props.foodPerPage} totalFood={props.totalSearchedFood} paginate={props.paginate} currentPage={props.currentPage} />
:<Pagination foodPerPage={props.foodPerPage} totalFood={props.totalFood} paginate={props.paginate} currentPage={props.currentPage} />
}
</div>
)
}
FoodListRow.js
export default function FoodListRow(props) {
// Edit food name:
const [editBtn, setEditBtn] = useState(false)
const handleEdit = () => {
setEditBtn(!editBtn)
}
// Update Food Name:
const [newFoodName, setNewFoodName] = useState('')
const updateFoodName = (id) => {
if (newFoodName) {
Axios.put("https://project.herokuapp.com/update", {
id: id,
newFoodName: newFoodName,
})
.catch(error => console.log(`The error is: ${error}`))
}
}
// Delete food:
const deleteFood = (id) => {
const confirm = window.confirm(`This action cannot be undone.\nAre you sure you want to delete this dish?`);
if(confirm === true){
Axios.delete(`https://project.herokuapp.com/delete/${id}`)
}
}
return (
<tr key={props.val._id}>
<td>
{props.val.foodName}
{editBtn &&
<div>
<input
type="text"
name="edit"
placeholder="New food name.."
autoComplete="off"
onChange={(event) => {setNewFoodName(event.target.value)}}
/>
<button
onClick={() => updateFoodName(props.val._id)}
>
✓
</button>
</div>
}
</td>
<td>{props.val.priceRange}</td>
<td>
<a
href={props.val.foodUrl}
target="_blank"
rel="noopener noreferrer"
>
</a>
<button
onClick={handleEdit}
>
✏️
</button>
<button
onClick={() => deleteFood(props.val._id)}
>
❌
</button>
</td>
</tr>
);
}
您永远不会更新食物名称的文本。在 FoodListRow 中,您应该为食物的名称创建一个状态。将其设置为 props.val.foodName,然后在 axios 请求后的 updateFoodName() 末尾更新它。
正如 Mohd Yashim Wong 提到的,每次后端发生变化时我们都需要重新渲染。
我放弃了 useEffect()
的依赖数组中的 foodList
并尝试了另一种方法,因为这不是重新呈现 axios 调用的正确方法。如果我使用这种方式,它只会无限期地发送 read
请求。那可能会很昂贵。
这是我切换到的:
- 我将依赖数组设置为空
- 从后端拉取数据,在axios调用后return到前端
addToList 函数:
const addToList = async (event) => {
event.preventDefault()
try {
await Axios.post(
"https://project.herokuapp.com/insert",
{
foodName: foodName,
isVegetarian: isVegetarian,
priceRange: priceRange,
foodUrl: foodUrl,
}
)
.then((response) => {
// Return the data to the UI:
setFoodList([...foodList, { _id: response.data._id, foodName: foodName, isVegetarian: isVegetarian, priceRange: priceRange, foodUrl: foodUrl }])
setFoodName('')
setIsVegetarian('no')
setPriceRange('$')
setFoodUrl('')
})
} catch(err) {
console.error(`There was an error while trying to insert - ${err}`)
}
}
updateFoodName 函数:
const updateFoodName = (id) => {
if (newFoodName) {
Axios.put("https://project.herokuapp.com/update", {
id: id,
newFoodName: newFoodName,
})
.then(() => {
// Update on searchedFood:
props.searchedFood.length > 0 ?
props.setSearchedFood(props.searchedFood.map((val) => {
return (
val._id === id ?
{
_id: id,
foodName: newFoodName,
isVegetarian: props.isVegetarian, priceRange: props.priceRange,
foodUrl: props.foodUrl,
} : val
)
})) //Update on foodList
: props.setFoodList(props.foodList.map((val) => {
return (
val._id === id ?
{
_id: id,
foodName: newFoodName,
isVegetarian: props.isVegetarian, priceRange: props.priceRange,
foodUrl: props.foodUrl,
} : val
)
}))
})
.catch(error => console.log(`Update name failed: ${error}`))
}
}
删除食物函数:
const deleteFood = (id) => {
const confirm = window.confirm(`This action cannot be undone.\nAre you sure you want to delete this dish?`);
if(confirm === true){
Axios.delete(`https://project.herokuapp.com/delete/${id}`)
.then(() => {
props.searchedFood.length > 0
? props.setSearchedFood(props.searchedFood.filter((val) => {
return val._id !== id
}))
: props.setFoodList(props.foodList.filter((val) => {
return val._id !== id
}))
})
}
}
我正在使用 MERN 堆栈构建此网站并遇到此呈现错误:
开始时,我有一个
foodList
table 渲染出数据库中的所有食物。我已经有一个
useEffect()
和依赖项数组中的foodList
- 所以用户随时更改foodList
table ( Add/Edit/Delete), 它会在不刷新页面的情况下立即渲染出添加的菜肴。当用户在此搜索和过滤栏中搜索内容时,它将隐藏
foodList
table 和 return 从foodList
数组中筛选出的searchedFood
的 table。但是当用户使用这个搜索和过滤功能然后尝试编辑/从
searchedFood
table中删除。它不会立即呈现更改 - 他们必须刷新页面才能看到所做的更改。
这可能与 useEffect()
有关,但我不知道如何在不中断 foodList
table 的情况下将其应用于 searchedFood
table .
App.js
export default function App() {
const [foodName, setFoodName] = useState('')
const [isVegetarian, setIsVegetarian] = useState('no')
const [priceRange, setPriceRange] = useState('$')
const [foodUrl, setFoodUrl] = useState('')
const [foodList, setFoodList] = useState([])
const [searchedFood, setSearchedFood] = useState([])
const [noResult, setNoResult] = useState(false)
// Display food list:
useEffect(() => {
let unmounted = false
Axios.get("https://project.herokuapp.com/read")
.then((response) => {
if (!unmounted) {
setFoodList(response.data)
}
})
.catch(error => {
console.log(`The error is: ${error}`)
return
})
return () => {
unmounted = true
}
}, [foodList])
// Add Food to list:
const addToList = async (event) => {//Axios.post logic in here}
// Paginate states:
const [currentPage, setCurrentPage] = useState(1)
const [foodPerPage] = useState(5)
// Get current food:
const indexOfLastFood = currentPage * foodPerPage
const indexOfFirstFood = indexOfLastFood - foodPerPage
const currentFood = foodList.slice(indexOfFirstFood, indexOfLastFood)
const currentSearchedFood = searchedFood.slice(indexOfFirstFood, indexOfLastFood)
const paginate = (pageNumber) => {
setCurrentPage(pageNumber)
}
return (
<section>
<FilterSearch
foodList={foodList}
searchedFood={searchedFood}
setSearchedFood={setSearchedFood}
noResult={noResult}
setNoResult={setNoResult}
paginate={paginate}
/>
{noResult ? <ResultNotFound/>
:
<FoodListTable
foodName={foodName}
priceRange={priceRange}
isVegetarian={isVegetarian}
foodUrl={foodUrl}
foodList={foodList}
currentFood={currentFood}
searchedFood={searchedFood}
currentSearchedFood={currentSearchedFood}
totalFood={foodList.length}
totalSearchedFood={searchedFood.length}
currentPage={currentPage}
paginate={paginate}
noResult={noResult}
foodPerPage={foodPerPage}
/>
}
</section>
)
}
FoodListTable.js
export default function FoodListTable(props) {
return (
<div>
<table>
<thead>
<tr>
<th>
Food name
</th>
<th>Price</th>
<th>
Action
</th>
</tr>
</thead>
<body>
// Return a table with data from searchFood on search:
{props.searchedFood.length > 0 ? props.currentSearchedFood.map((val) => {
return (
<FoodListRow
val={val}
key={val._id}
foodName={val.foodName}
isVegetarian={val.isVegetarian}
priceRange={val.priceRange}
foodUrl={val.foodUrl}
/>
)
}) : props.currentFood.map((val) => { // If not on search, return a table with data from foodList:
return (
<FoodListRow
val={val}
key={val._id}
foodName={val.foodName}
isVegetarian={val.isVegetarian}
priceRange={val.priceRange}
foodUrl={val.foodUrl}
/>
)
})
}
</tbody>
</table>
// Display different Pagination on searched table and food list table:
{props.searchedFood.length > 0 ?
<Pagination foodPerPage={props.foodPerPage} totalFood={props.totalSearchedFood} paginate={props.paginate} currentPage={props.currentPage} />
:<Pagination foodPerPage={props.foodPerPage} totalFood={props.totalFood} paginate={props.paginate} currentPage={props.currentPage} />
}
</div>
)
}
FoodListRow.js
export default function FoodListRow(props) {
// Edit food name:
const [editBtn, setEditBtn] = useState(false)
const handleEdit = () => {
setEditBtn(!editBtn)
}
// Update Food Name:
const [newFoodName, setNewFoodName] = useState('')
const updateFoodName = (id) => {
if (newFoodName) {
Axios.put("https://project.herokuapp.com/update", {
id: id,
newFoodName: newFoodName,
})
.catch(error => console.log(`The error is: ${error}`))
}
}
// Delete food:
const deleteFood = (id) => {
const confirm = window.confirm(`This action cannot be undone.\nAre you sure you want to delete this dish?`);
if(confirm === true){
Axios.delete(`https://project.herokuapp.com/delete/${id}`)
}
}
return (
<tr key={props.val._id}>
<td>
{props.val.foodName}
{editBtn &&
<div>
<input
type="text"
name="edit"
placeholder="New food name.."
autoComplete="off"
onChange={(event) => {setNewFoodName(event.target.value)}}
/>
<button
onClick={() => updateFoodName(props.val._id)}
>
✓
</button>
</div>
}
</td>
<td>{props.val.priceRange}</td>
<td>
<a
href={props.val.foodUrl}
target="_blank"
rel="noopener noreferrer"
>
</a>
<button
onClick={handleEdit}
>
✏️
</button>
<button
onClick={() => deleteFood(props.val._id)}
>
❌
</button>
</td>
</tr>
);
}
您永远不会更新食物名称的文本。在 FoodListRow 中,您应该为食物的名称创建一个状态。将其设置为 props.val.foodName,然后在 axios 请求后的 updateFoodName() 末尾更新它。
正如 Mohd Yashim Wong 提到的,每次后端发生变化时我们都需要重新渲染。
我放弃了 useEffect()
的依赖数组中的 foodList
并尝试了另一种方法,因为这不是重新呈现 axios 调用的正确方法。如果我使用这种方式,它只会无限期地发送 read
请求。那可能会很昂贵。
这是我切换到的:
- 我将依赖数组设置为空
- 从后端拉取数据,在axios调用后return到前端
addToList 函数:
const addToList = async (event) => {
event.preventDefault()
try {
await Axios.post(
"https://project.herokuapp.com/insert",
{
foodName: foodName,
isVegetarian: isVegetarian,
priceRange: priceRange,
foodUrl: foodUrl,
}
)
.then((response) => {
// Return the data to the UI:
setFoodList([...foodList, { _id: response.data._id, foodName: foodName, isVegetarian: isVegetarian, priceRange: priceRange, foodUrl: foodUrl }])
setFoodName('')
setIsVegetarian('no')
setPriceRange('$')
setFoodUrl('')
})
} catch(err) {
console.error(`There was an error while trying to insert - ${err}`)
}
}
updateFoodName 函数:
const updateFoodName = (id) => {
if (newFoodName) {
Axios.put("https://project.herokuapp.com/update", {
id: id,
newFoodName: newFoodName,
})
.then(() => {
// Update on searchedFood:
props.searchedFood.length > 0 ?
props.setSearchedFood(props.searchedFood.map((val) => {
return (
val._id === id ?
{
_id: id,
foodName: newFoodName,
isVegetarian: props.isVegetarian, priceRange: props.priceRange,
foodUrl: props.foodUrl,
} : val
)
})) //Update on foodList
: props.setFoodList(props.foodList.map((val) => {
return (
val._id === id ?
{
_id: id,
foodName: newFoodName,
isVegetarian: props.isVegetarian, priceRange: props.priceRange,
foodUrl: props.foodUrl,
} : val
)
}))
})
.catch(error => console.log(`Update name failed: ${error}`))
}
}
删除食物函数:
const deleteFood = (id) => {
const confirm = window.confirm(`This action cannot be undone.\nAre you sure you want to delete this dish?`);
if(confirm === true){
Axios.delete(`https://project.herokuapp.com/delete/${id}`)
.then(() => {
props.searchedFood.length > 0
? props.setSearchedFood(props.searchedFood.filter((val) => {
return val._id !== id
}))
: props.setFoodList(props.foodList.filter((val) => {
return val._id !== id
}))
})
}
}