reactjs 为什么在初始组件渲染时更新状态?

rqdpfwrv  于 2023-03-01  发布在  React
关注(0)|答案(3)|浏览(117)

我有一个应用程序,可以搜索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;
kg7wmglp

kg7wmglp1#

我希望postExercise()函数仅在selectedExercise状态改变时调用
这不是useEffect的工作方式。它会在每次依赖列表中的值改变时调用你的效果。如果你想阻止最初的调用,你必须先考虑在挂载时触发的效果。

ee7vknir

ee7vknir2#

在执行API数据搜索时更新状态的原因是,每次呈现组件时,都会执行负责获取已保存练习的效果挂钩。此效果挂钩不依赖于任何状态,因此它在每次呈现时运行,并更新fetchedData的状态。由于savedFetchedName派生自fetchedData,因此它也会在每次呈现时更新。
然后,当selectedExercise状态由于ExerciseCard中的"Save"按钮点击而改变时,postExercise效果挂钩再次运行。由于savedFetchedName在每次渲染时都被更新,因此每次都是一个新对象,并且postExercise挂钩被调用。这就是为什么在执行API数据搜索时状态会被更新的原因。
若要解决此问题,你可以将负责提取已保存练习的逻辑移到将 Package 你的App组件的单独组件(如SavedExercisesProvider)。这样,提取逻辑将仅运行一次,并且不会在每次呈现时更新状态。
下面是SavedExercisesProvider组件的一个示例:

import React, { useState, useEffect, createContext } from 'react';

export const SavedExercisesContext = createContext([]);

const SavedExercisesProvider = ({ children }) => {
  const [fetchedData, setFetchedData] = useState([]);

  useEffect(() => {
    fetch("http://localhost:3001/savedexercises")
      .then((res) => {
        return res.json();
      })
      .then((data) => {
        setFetchedData(data);
      });
    console.log("saved exercises fetched");
  }, []);

  const savedFetchedName = fetchedData.map((fetched) => fetched.name);

  return (
    <SavedExercisesContext.Provider value={savedFetchedName}>
      {children}
    </SavedExercisesContext.Provider>
  );
};

export default SavedExercisesProvider;

用法:

import SavedExercisesProvider from './SavedExercisesProvider';

function MainApp() {
  return (
    <SavedExercisesProvider>
      <App />
    </SavedExercisesProvider>
  );
}

export default MainApp;
uujelgoq

uujelgoq3#

savedFetchedName变量依赖于fetchedData状态,这意味着每当fetchedData状态更改时,它将导致组件重新呈现,因此savedFetchedName将更改。

要避免此问题:

  • 您可以将savedFetchedName设置为状态。
  • 更新useEffect内的savedFetchedName(仅取决于获取的数据)。
const Exercises = ({ exercises }) => {
   const [currentPage, setCurrentPage] = useState(1);
   const exercisesPerPage = 24;
   const [fetchedData, setFetchedData] = useState([]);
   const [savedFetchedName, setSavedFetchedName] = useState([]);
   //....
   useEffect(() => {
       setSavedFetchedName(fetchedData.map((fetched) => fetched.name));
   }, [fetchedData]);
   //...
   }

相关问题