我有一个应用程序,可以搜索API调用的结果并显示数据。用户可以选择将练习保存到数据库中,以便以后查看。
为什么在执行API数据搜索时要更新状态?我希望postExercise()函数只有在selectedExercise状态发生变化时才能通过useEffect调用(在ExerciseCard组件中单击保存按钮时),否则一切正常。
练习部分
import { Grid, Typography, Container, Pagination, Button } from "@mui/material";
import { Box } from "@mui/system";
import React, { useState, useEffect, useContext } from "react";
import ExerciseCard from "./ExerciseCard";
import { SavedExercisesContext } from "../App.js";
const Exercises = ({ exercises }) => {
const [currentPage, setCurrentPage] = useState(1);
const exercisesPerPage = 24;
const [fetchedData, setFetchedData] = useState([]);
const [selectedExercise] = useContext(SavedExercisesContext);
// fetches saved exercises
useEffect(() => {
fetch("http://localhost:3001/savedexercises")
.then((res) => {
return res.json();
})
.then((data) => {
setFetchedData(data);
});
console.log("saved exercises fetched");
}, []);
// maps over fetchedData and saves to a variable
const savedFetchedName = fetchedData.map((fetched) => fetched.name);
// checks if selected exercise exists in database when Add button is clicked
useEffect(() => {
const postExercise = () => {
if (savedFetchedName.includes(selectedExercise.name)) {
console.log("already added exercise");
} else {
console.log("adding new exercise");
fetch("http://localhost:3001/savedExercises", {
method: "POST",
body: JSON.stringify(selectedExercise),
headers: { "Content-Type": "application/json" },
});
}
};
postExercise();
}, [selectedExercise]);
// pagination
const lastIndex = currentPage * exercisesPerPage;
const firstIndex = lastIndex - exercisesPerPage;
const currentExercises = exercises.slice(firstIndex, lastIndex);
const handlePagination = (event, value) => {
setCurrentPage(value);
};
return (
<Box>
<Typography variant="h4" color="black" sx={{ p: 3 }}>
Search Results
</Typography>
<Container maxWidth="xl">
<Grid container spacing={1}>
{currentExercises?.map((exercise, id) => (
<Grid item xs={12} md={4} key={exercise.id}>
<ExerciseCard
key={id}
exercise={exercise}
savedFetchedName={savedFetchedName}
/>
</Grid>
))}
</Grid>
</Container>
<Box sx={{ display: "flex", justifyContent: "center", p: 3 }}>
<Pagination
count={Math.ceil(exercises.length / exercisesPerPage)}
color="error"
onChange={handlePagination}
/>
</Box>
<Box sx={{ display: "flex", justifyContent: "center", p: 3 }}>
<Button variant="contained" color="error" onClick={handleScroll}>
Back to Top
</Button>
</Box>
</Box>
);
};
export default Exercises;
练习卡组件
import {
Button,
Card,
CardContent,
CardMedia,
Container,
Typography,
} from "@mui/material";
import { Box } from "@mui/system";
import React, { useContext } from "react";
import { SavedExercisesContext } from "../App.js";
const ExerciseCard = ({ exercise }) => {
const [selectedExercise, setSelectedExercise] = useContext(
SavedExercisesContext
);
const saveExerciseToDatabase = () => {
setSelectedExercise({
apiId: exercise.id,
name: exercise.name,
target: exercise.target,
gifUrl: exercise.gifUrl,
});
};
return (
<Container maxWidth="xl">
<Box>
<Card>
<CardMedia
component="img"
alt={exercise.name}
image={exercise.gifUrl}
/>
<CardContent sx={{ pb: 2, height: "75px" }}>
<Typography variant="h5" sx={{ pb: 1 }}>
{exercise.name.toUpperCase()}
</Typography>
<Typography variant="body2">
{exercise.target.toUpperCase()}
</Typography>
</CardContent>
<Box>
<Box>
<Button
variant="contained"
color="error"
size="medium"
sx={{ m: 2 }}
onClick={() => saveExerciseToDatabase()}
>
Save
</Button>
</Box>
</Box>
</Card>
</Box>
</Container>
);
};
export default ExerciseCard;
App.js
import "./App.css";
import { Box } from "@mui/system";
import React, { useState } from "react";
import Navbar from "./components/Navbar";
import { Route, Routes } from "react-router-dom";
import Home from "./pages/Home";
import SavedExercisesPage from "./pages/SavedExercisesPage";
export const SavedExercisesContext = React.createContext();
const App = () => {
const [selectedExercise, setSelectedExercise] = useState([]);
return (
<SavedExercisesContext.Provider
value={[selectedExercise, setSelectedExercise]}
>
<Box>
<Navbar />
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/SavedExercisesPage" element={<SavedExercisesPage />} />
</Routes>
</Box>
</SavedExercisesContext.Provider>
);
};
export default App;
3条答案
按热度按时间kg7wmglp1#
我希望postExercise()函数仅在selectedExercise状态改变时调用
这不是useEffect的工作方式。它会在每次依赖列表中的值改变时调用你的效果。如果你想阻止最初的调用,你必须先考虑在挂载时触发的效果。
ee7vknir2#
在执行API数据搜索时更新状态的原因是,每次呈现组件时,都会执行负责获取已保存练习的效果挂钩。此效果挂钩不依赖于任何状态,因此它在每次呈现时运行,并更新fetchedData的状态。由于savedFetchedName派生自fetchedData,因此它也会在每次呈现时更新。
然后,当selectedExercise状态由于ExerciseCard中的"Save"按钮点击而改变时,postExercise效果挂钩再次运行。由于savedFetchedName在每次渲染时都被更新,因此每次都是一个新对象,并且postExercise挂钩被调用。这就是为什么在执行API数据搜索时状态会被更新的原因。
若要解决此问题,你可以将负责提取已保存练习的逻辑移到将 Package 你的App组件的单独组件(如SavedExercisesProvider)。这样,提取逻辑将仅运行一次,并且不会在每次呈现时更新状态。
下面是SavedExercisesProvider组件的一个示例:
用法:
uujelgoq3#
savedFetchedName变量依赖于fetchedData状态,这意味着每当fetchedData状态更改时,它将导致组件重新呈现,因此savedFetchedName将更改。
要避免此问题: