如何在 React 中即时显示组件的变化?

How can I display changes in component instantly in React?

我正在使用 MERN 堆栈构建此网站并遇到此呈现错误:

这可能与 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
            }))
          })
        }
    }