0

I am using JSON Server, Material UI and React to saved exercises from Rapid API. When I click the add button for an exercise, an empty array is being saved and is breaking my code on the saved exercises page. This is only happening every so often; if I re click the same exerciser a few time it saves it as expected. I'm not sure, but I think the problem might be with the useState hook not registering on the initial button click. Is this the problem? If so, is there a way to work around this bug?

import {
  Button,
  Card,
  CardContent,
  CardMedia,
  Container,
  Typography,
} from "@mui/material";
import { Box } from "@mui/system";
import React, { useEffect, useState } from "react";
const ExerciseCard = ({ exercise }) => {
  const [selectedExercise, setSelectedExercise] = useState([]);
  const [selectedExerciseName, setSelectedExerciseName] = useState();
  const [fetchedData, setFetchedData] = useState([]);
  useEffect(() => {
    fetch("http://localhost:3001/savedexercises")
      .then((res) => {
        return res.json();
      })
      .then((data) => {
        setFetchedData(data);
        return fetchedData;
      });
  }, [fetchedData]);
  const addExerciseToDB = async () => {
    const savedFetchedName = fetchedData.map((fetched) => fetched.name);
    setSelectedExercise([]);
    setSelectedExercise({
      apiId: exercise.id,
      name: exercise.name,
      target: exercise.target,
      gifUrl: exercise.gifUrl,
    });
    setSelectedExerciseName(exercise.name);
    if (savedFetchedName.includes(selectedExerciseName)) {
      console.log("already added exercise");
    } else {
      console.log("adding new exercise");
      await fetch("http://localhost:3001/savedExercises", {
        method: "POST",
        body: JSON.stringify(selectedExercise),
        headers: { "Content-Type": "application/json" },
      });
    }
  };
  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={() => addExerciseToDB()}
              >
                Add
              </Button>
            </Box>
          </Box>
        </Card>
      </Box>
    </Container>
  );
};
export default ExerciseCard;
postmetric
  • 69
  • 6
  • Does this answer your question? [The useState set method is not reflecting a change immediately](https://stackoverflow.com/questions/54069253/the-usestate-set-method-is-not-reflecting-a-change-immediately). You are calling `setSelectedExercise` and `setSelectedExerciseName` in your `addExerciseToDB` method and you are trying to use `selectedExerciseName` and `selectedExercise` just after setting them, which will not work as you expect. – Sergey Sosunov Jan 23 '23 at 16:18
  • @SergeySosunov thanks for the reply. yes, that does help some. I guess I'm stuck on which part of the code needs to be included in the useEffect. I am pretty sure that I will need to pass `selectedExercise` as the dependency since that is the state that should be updated each time the post is being made. – postmetric Jan 24 '23 at 21:58
  • I think you overcomplicated your code a bit. Like why do you need `selectedExercise` and `selectedExerciseName` at all, if you are not using them anywhere in the JSX and their usage in useEffects is not really... a good idea, to be honest. Next question - why do you need to use `fetchedData` in the card itself, meaning if you have 10 cards - each of them will fire 1 `fetchData` things to load the same data, i guess it would be better to move the fetch on the 1 level up, to the component that map-renders them and provide this data as an additional parameter to each `ExerciseCard`. – Sergey Sosunov Jan 24 '23 at 22:13
  • Also, you can add additional parameter to your `ExerciseCard` component - onClick, and handle the saving and re-fetching the data on the parent level, but probably im missing the context and full picture, so dont mind :) – Sergey Sosunov Jan 24 '23 at 22:16
  • And also, you can exctract the fetching and setState logic from useEffect to a separate function inside of your component, and just call this function in 1) useEffect as previously and 2) just after POST request, for example. Also, when you are using useEffect - if you are setting something in it - ensure this "something" is not inside of the dependency array due to it could lead to circular calls. – Sergey Sosunov Jan 24 '23 at 22:19
  • @SergeySosunov I have a component that searches through an api and returns a list of exercises base on the search terms. once I have the searched exercises, I am giving the option to save those to be recalled on another "saved exercises" page. I am basically trying to make sure that the exercise that is selected does not already exist in the database. that's why i am fetching the saved exercises in the cards, to make sure they are not already saved there, preventing duplicates. – postmetric Jan 24 '23 at 23:57
  • Basically you can fetch saved cards as a 1st step in your `addExerciseToDB` function. Like when clicked - lock the control, do GET fetch, check if card is not saved yet, do POST save, unlock the control. – Sergey Sosunov Jan 25 '23 at 00:35
  • @SergeySosunov so I fetch in the ExerciseCard and do all the other stuff there too? what do you mean by "locking and unlocking control"? – postmetric Jan 25 '23 at 00:51
  • You need to try to implement atomic operation and prevent possible race conditions. And by disabling i mean something like [this](https://stackoverflow.com/questions/71284849/how-to-disable-the-login-button-when-an-api-request-is-already-in-progress-in-re), so user wont be able to press 10 times and execute 20 api requests until "operation" is complete. – Sergey Sosunov Jan 25 '23 at 00:58

0 Answers0