skip to Main Content

I have a mern application that allows users to report match results. I use a modal to create the result. Once the report is entered the data ir refetched on the compeitor page and the report shows up in the list.

If I edit the report it changes in the database and the data even changes in the report list, but if I click the edit button again without refreshing the page, the modal still shows the old data.

I thought maybe the modal was the issue so for the editReport, I tried it on a regular page but got the same result. I use refetch in the onSubmit which is passed from the competition so that data is refetched and updated when the modal closes, but it still doesn’t help with the data for the modal.

Here is the competitor page that lists the records with the edit button to open the modal.

import React, { useState } from "react";
import moment from "moment";
import Loader from "../Loader";
import { Button, Col, Row, Table } from "react-bootstrap";
import { useGetAthleteMatchReportsQuery } from "../../slices/reportsApiSlice";
import { useGetMyTeamsQuery } from "../../slices/athleteApiSlice";
import AddReportModal from "../AddReportModal";

// icons
import { GrEdit } from "react-icons/gr";
import EditReportModal from "../EditReportModal";

const CompetitionInfo = ({ user }) => {
  // const [showMatchModal, setShowMatchModal] = useState(false);

  // const handleMatchShow = () => setShowMatchModal(true);
  // const handleMatchClose = () => setShowMatchModal(false);

  const [showReportModal, setShowReportModal] = useState(false);

  const handleReportShow = () => setShowReportModal(true);
  const handleReportClose = () => setShowReportModal(false);

  const [showEditReportModal, setShowEditReportModal] = useState(false);

  const handleEditReportShow = () => setShowEditReportModal(true);
  const handleEditReportClose = () => setShowEditReportModal(false);

  const [editReportId, setEditReportId] = useState("");

  const handleEditClick = (reportId) => {
    setEditReportId(reportId);
  };

  const {
    data: myMatchReports,
    isLoading,
    refetch,
  } = useGetAthleteMatchReportsQuery(user.athlete);

  const {
    data: myTeams,
    isLoading: teamLoading,
    refetch: teamsRefetch,
  } = useGetMyTeamsQuery(user.athlete);

  !teamLoading && console.log("my teams ", myTeams);
  !isLoading && console.log("match reports ", myMatchReports);
  return (
    <div>
      <h1>My Competition Info</h1>
      <Button onClick={handleReportShow} className="basic_btn my-3">
        Add a Match
      </Button>
      <br />
      {isLoading && <Loader />}

      <h2>Table for match results from reports table</h2>
      <Table striped bordered hover responsive>
        <thead>
          <tr>
            <th className="align-items-center">Match Type</th>
            <th className="align-items-center">Event Name</th>
            <th className="align-items-center">Event Date</th>
            <th className="align-items-center">Opponent Name</th>
            <th className="align-items-center">Result</th>
            <th className="align-items-center">Edit</th>
          </tr>
        </thead>

        <tbody>
          {myMatchReports && myMatchReports.length > 0 ? (
            myMatchReports.map(
              (report) =>
                report.reportType === "Result" && (
                  <tr key={report._id}>
                    <td className="table_bold_text">{report.reportType}</td>
                    <td>
                      {console.log(report.eventEndDate)}
                      {report.eventName.length > 25
                        ? `${report.eventName.slice(0, 25)}...`
                        : report.eventName}
                    </td>
                    <td className="table_bold_text">
                      {report.eventStartDate && report.eventEndDate
                        ? `${moment(report.eventStartDate).format(
                            "M-D-YYYY"
                          )} - ${moment(report.eventEndDate).format(
                            "M-D-YYYY"
                          )}`
                        : report.eventStartDate
                        ? moment(report.eventStartDate).format("M-D-YYYY")
                        : "No date"}
                    </td>
                    <td className="table_bold_text">{report.opponentName}</td>
                    <td className="table_bold_text">{report.result}</td>
                    <td className="table_bold_text">
                      <GrEdit
                        type="button"
                        onClick={() => {
                          handleEditClick(report._id);
                          handleEditReportShow();
                        }}
                      />
                    </td>
                  </tr>
                )
            )
          ) : (
            <tr>
              <td colSpan="4">
                No matches found
                <Button onClick={handleReportShow} className="basic_btn my-3">
                  Add a Match
                </Button>
              </td>
            </tr>
          )}
        </tbody>
      </Table>

      <Row className="py-3">
        <Col>
          <AddReportModal
            show={showReportModal}
            handleClose={handleReportClose}
            athleteId={user.athlete}
            myTeams={myTeams}
            refetch={refetch}
          />
        </Col>
      </Row>

      <Row className="py-3">
        <Col>
          <EditReportModal
            show={showEditReportModal}
            handleClose={handleEditReportClose}
            editReportId={editReportId}
            refetch={refetch}
          />
        </Col>
      </Row>
    </div>
  );
};

export default CompetitionInfo;

Here is the EditReportModal:

import { useEffect, useRef, useState } from "react";
import { Button, Modal, Row, Col } from "react-bootstrap";
import FormContainer from "./FormContainer";
import { Form, Formik } from "formik";
import { Tooltip } from "react-tooltip";
import Countries from "../assets/countries.json";
import {
  useGetReportByIdQuery,
  useUpdateReportMutation,
} from "../slices/reportsApiSlice";
import { createReport } from "../schemas";
import moment from "moment";
import CustomRadio from "./inputs/CustomRadio";
import CustomSelect from "./inputs/CustomSelect";
import CustomInput from "./inputs/CustomInput";
import CustomDatePicker from "./inputs/CustomDatePicker";
import CustomTextarea from "./inputs/CustomTextarea";
import CustomCheckbox from "./inputs/CustomCheckbox";
import { toast } from "react-toastify";
import Loader from "./Loader";

// icons
import { BsFillQuestionDiamondFill } from "react-icons/bs";

const EditReportModal = ({ show, handleClose, editReportId, refetch }) => {
  const { data: myReport, isLoading } = useGetReportByIdQuery(editReportId);

  const [athlete, setAthlete] = useState("");
  const [reportType, setReportType] = useState("");
  const [matchType, setMatchType] = useState("");
  const [eventName, setEventName] = useState("");
  const [eventStartDate, setEventStartDate] = useState("");
  const [eventEndDate, setEventEndtDate] = useState("");
  const [opponentName, setOpponentName] = useState("");
  const [weightCategory, setWeightCategory] = useState("");
  const [opponentClub, setOpponentClub] = useState("");
  const [opponentCountry, setOpponentCountry] = useState("");
  const [opponentRank, setOpponentRank] = useState("");
  const [opponentHandedness, setOpponentHandedness] = useState("");
  const [opponentAttacks, setOpponentAttacks] = useState("");
  const [opponentAttackNotes, setOpponentAttackNotes] = useState("");
  const [athleteAttacks, setAthleteAttacks] = useState("");
  const [athleteAttackNotes, setAthleteAttackNotes] = useState("");
  const [result, setResult] = useState("");
  const [score, setScore] = useState("");
  const [isPublic, setIsPublic] = useState("");

  useEffect(() => {
    if (myReport) {
      setReportType(myReport?.reportType);
      setMatchType(myReport?.matchType);
      setEventName(myReport?.eventName);
      setWeightCategory(myReport?.weightCategory);
      setEventStartDate(myReport?.eventStartDate);
      setEventEndtDate(myReport?.eventEndDate);
      setOpponentName(myReport?.opponentName);
      setOpponentClub(myReport?.opponentClub);
      setOpponentCountry(myReport?.opponentCountry);
      setOpponentRank(myReport?.opponentRank);
      setOpponentHandedness(myReport?.opponentHandedness);
      setOpponentAttacks(myReport?.opponentAttacks);
      setOpponentAttackNotes(myReport?.opponentAttackNotes);
      setAthleteAttacks(myReport?.athleteAttacks);
      setAthleteAttackNotes(myReport?.athleteAttackNotes);
      setResult(myReport?.result);
      setScore(myReport?.score);
      setIsPublic(myReport?.isPublic);
    }
  }, [myReport]);
  console.log("line 74 result ", result);
  const [updateReport, { isLoading: updateLoading }] =
    useUpdateReportMutation();

  const onSubmit = async (values, action) => {
    try {
      const res = await updateReport({
        editReportId,
        reportType: reportType,
        matchType: values.matchType,
        eventName: values.eventName,
        eventStartDate: values.eventStartDate,
        eventEndDate: values.eventEndDate,
        opponentName: values.opponentName,
        weightCategory: values.weightCategory,
        opponentClub: values.opponentClub,
        opponentCountry: values.opponentCountry,
        opponentRank: values.opponentRank,
        opponentHandedness: values.opponentHandedness,
        opponentAttacks: values.opponentAttacks,
        opponentAttackNotes: values.opponentAttackNotes,
        athleteAttacks: values.athleteAttacks,
        athleteAttackNotes: values.athleteAttackNotes,
        result: values.result,
        score: values.score,
        isPublic: values.isPublic,
      }).unwrap();
      refetch();

      handleClose();
    } catch (err) {
      toast.error(err?.data?.message || err.message);
    }
  };

  const windowSize = useRef([window.innerWidth, window.innerHeight]);
  const tooltipWidth = windowSize.current[0] > 500 ? "50vw" : "90vw";
  console.log("result ", result);
  return (
    <Modal size="xl" show={show} onHide={handleClose}>
      <Modal.Header closeButton>
        <Modal.Title>Add Match</Modal.Title>
      </Modal.Header>
      <Modal.Body>
        {isLoading && <Loader />}
        <FormContainer>
          <Formik
            enableReinitialize={true}
            initialValues={{
              //athlete: "",
              reportType: reportType,
              matchType: matchType,
              eventName: eventName,
              eventStartDate: "",
              eventEndDate: "",
              opponentName: opponentName,
              weightCategory: weightCategory,
              opponentClub: opponentClub,
              opponentCountry: opponentCountry,
              opponentRank: opponentRank,
              opponentHandedness: opponentHandedness,
              opponentAttacks: opponentAttacks,
              opponentAttackNotes: opponentAttackNotes,
              athleteAttacks: athleteAttacks,
              athleteAttackNotes: athleteAttackNotes,
              result: String(result),
              score: score,
              isPublic: isPublic,
            }}
            validateSchema={createReport}
            onSubmit={onSubmit}
          >
            {({ isSubmitting }) => (
              <Form>
                <Row>
                  <Col className="d-flex flex-column my-2">
                    <label>
                      Report Type
                      <BsFillQuestionDiamondFill
                        data-tooltip-id="report-type-tooltip"
                        className="ms-2"
                      />
                      <Tooltip
                        id="report-type-tooltip"
                        style={{
                          width: `${tooltipWidth}`,
                          zIndex: "9999",
                        }}
                      >
                        <div
                          style={{
                            display: "flex",
                            flexDirection: "column",
                          }}
                        >
                          Select Result if you only want to record match
                          results.
                          <br />
                          Select Scouting if you are scouting an athlete for
                          future matches
                          <br />
                          Select both if you want to record results and scout
                          the opponent for futuer matches.
                        </div>
                      </Tooltip>
                    </label>
                    <div className="d-flex flex-column align-items-start">
                      <CustomRadio
                        label="Result"
                        value="Result"
                        name="reportType"
                        placeholder="Result"
                        checked={reportType === "Result"}
                      />
                      <CustomRadio
                        label="Scouting"
                        value="Scouting"
                        name="reportType"
                        placeholder="Scouting"
                        checked={reportType === "Scouting"}
                      />
                      <CustomRadio
                        label="Both"
                        value="Both"
                        name="reportType"
                        placeholder="Both"
                        checked={reportType === "Both"}
                      />
                    </div>
                  </Col>
                </Row>

                <Row>
                  <Col className="d-flex flex-column my-2">
                    <CustomSelect
                      label="Match Type"
                      name="matchType"
                      placeholder="Select match type..."
                      value={matchType}
                    >
                      <option value="">Select event type...</option>
                      <option value="BJJ">Brazilian Jiu Jitsu</option>
                      <option value="Judo">Judo</option>
                      <option value="Wrestling">Wrestling</option>
                    </CustomSelect>
                  </Col>
                </Row>

                <Row>
                  <Col className="d-flex flex-column my-2">
                    <CustomInput
                      label="Event Name"
                      name="eventName"
                      type="text"
                      placeholder="Enter event name"
                    />
                  </Col>
                </Row>

                <Row>
                  <Col className="d-flex flex-column my-2">
                    <CustomDatePicker
                      label="Event Start Date"
                      name="eventStartDate"
                    />
                  </Col>
                  <Col className="d-flex flex-column my-2">
                    <CustomDatePicker
                      label="Event End Date"
                      name="eventEndDate"
                    />
                  </Col>
                </Row>

                <Row>
                  <Col className="d-flex flex-column my-2">
                    <CustomInput
                      label="Weight Category"
                      name="weightCategory"
                      type="text"
                    />
                  </Col>
                </Row>

                <Row>
                  <Col className="d-flex flex-column my-2">
                    <CustomInput
                      label="Opponent Name"
                      name="opponentName"
                      type="text"
                    />
                  </Col>
                  <Col className="d-flex flex-column my-2">
                    <CustomInput
                      label="Opponent Club"
                      name="opponentClub"
                      type="text"
                    />
                  </Col>
                </Row>

                <Row>
                  <Col className="d-flex flex-column my-3">
                    <CustomInput
                      label="Opponent Rank"
                      name="opponentRank"
                      type="text"
                    />
                  </Col>
                  <Col className="d-flex flex-column my-3">
                    <label>Primarily right or left handed</label>
                    <div className="d-flex flex-column align-items-start">
                      <CustomRadio
                        label="Righty"
                        value="righty"
                        name="opponentHandedness"
                      />
                      <CustomRadio
                        label="Lefty"
                        value="lefty"
                        name="opponentHandedness"
                      />
                    </div>
                  </Col>
                </Row>

                <Row>
                  <Col className="d-flex flex-column my-2">
                    <CustomSelect
                      label="Country"
                      name="opponentCountry"
                      placeholder="Select country..."
                      value={opponentCountry}
                    >
                      <option value="">Select country...</option>

                      <option value="">----------</option>
                      {Countries.map((country) => (
                        <option key={country.code3} value={country.code3}>
                          {country.name}
                        </option>
                      ))}
                    </CustomSelect>
                  </Col>
                </Row>

                <Row>
                  <Col className="d-flex flex-column my-3">
                    <CustomTextarea
                      label="Opponents attacks"
                      type="textarea"
                      name="opponentAttacks"
                      placeholder="Enter opponent's attacks"
                    />
                  </Col>
                </Row>

                <Row>
                  <Col className="d-flex flex-column my-3">
                    <CustomTextarea
                      label="Notes on opponents attacks"
                      type="textarea"
                      name="opponentAttackNotes"
                      placeholder="Enter notes on opponent's attacks"
                    />
                  </Col>
                </Row>

                <Row>
                  <Col className="d-flex flex-column my-3">
                    <CustomTextarea
                      label="My/My Athlete's attacks"
                      type="textarea"
                      name="athleteAttacks"
                      placeholder="Enter My/My Athlete's attacks"
                    />
                  </Col>
                </Row>

                <Row>
                  <Col className="d-flex flex-column my-3">
                    <CustomTextarea
                      label="Notes on my/my athlete's attacks"
                      type="textarea"
                      name="athleteAttackNotes"
                      placeholder="Enter notes on opponent's attacks"
                    />
                  </Col>
                </Row>

                <Row>
                  <Col className="d-flex flex-column my-3">
                    <label>Outcome</label>
                    <div className="d-flex flex-column align-items-start">
                      <CustomRadio label="Won" value="Won" name="result" />
                      <CustomRadio label="Lost" value="Lost" name="result" />
                    </div>
                  </Col>

                  <Col className="d-flex flex-column my-3">
                    <CustomInput label="Score" name="score" type="text" />
                  </Col>
                </Row>

                <Row>
                  <Col className="d-flex flex-column my-3">
                    <CustomCheckbox
                      type="checkbox"
                      text="Make this report public"
                      name="isPublic"
                      tooltipText="Make this report public"
                    />
                  </Col>
                </Row>

                <Row>
                  <Col className="d-flex flex-column align-items-center gap-2">
                    <Button
                      disabled={isSubmitting}
                      type="submit"
                      className="login_btn"
                    >
                      Add Report
                    </Button>
                  </Col>
                </Row>
              </Form>
            )}
          </Formik>
        </FormContainer>
      </Modal.Body>
    </Modal>
  );
};

export default EditReportModal;

2

Answers


  1. Chosen as BEST ANSWER

    I don't know if this is the best way to handle this, but I was able to get it to work by refetching when the modal loads. I renamed refetch to localRefetch because I have refetch being pass to the modal to refetch the data on the competitor page when the form is submitted from the modal.

    Again, I'm not sure if it's the best practice, but it seems to be working.

    const {
        data: myReport,
        isLoading,
        refetch: localRefetch,
      } = useGetReportByIdQuery(editReportId);
    
      useEffect(() => {
        localRefetch();
      });
    

  2. I have tried to replicate your use case of the edit form. Your use case is that your form 1st initialized with stale/dummy data, and once the API call is successful, it shows the real data. I suspect that you don’t need useState to store each value. Will still try to solve with and without it.

    Method 1: Don’t show the form until the data is loaded.

    Benefits:

    • Form always loads with the actual data.
    • No need for useState/useEffect to store the data separately. Directly load the data from the API response.
    import { useMutation, useQuery } from "@tanstack/react-query";
    import { useState } from "react";
    import Button from "react-bootstrap/Button";
    import Modal from "react-bootstrap/Modal";
    import { faker } from "@faker-js/faker";
    import { Formik, Form } from "formik";
    
    const useEditQuery = (id, enabled) =>
      useQuery({
        queryKey: ["testKey", id],
        queryFn: () =>
          new Promise((resolve) => {
            setTimeout(
              () =>
                resolve({
                  matchType: faker.person.fullName(),
                  eventName: faker.person.fullName(),
                }),
              1000
            );
          }),
        enabled,
      });
    
    const useMutateExample = () =>
      useMutation({
        mutationFn: (payload) =>
          new Promise((resolve) => {
            setTimeout(
              () =>
                resolve({
                  status: "success",
                  result: payload,
                }),
              1000
            );
          }),
      });
    
    function EditModal({ show, handleClose, dataId }) {
      const { data, isFetching } = useEditQuery(dataId, show);
      const mutateData = useMutateExample();
      return (
        <>
          <Modal show={show} onHide={handleClose}>
            <Modal.Header closeButton>
              <Modal.Title>Modal heading</Modal.Title>
            </Modal.Header>
            <Modal.Body>
              {isFetching && <div>Fetching data</div>}
              {!isFetching && (
                <Formik
                  initialValues={{
                    matchType: data?.matchType,
                    eventName: data?.eventName,
                  }}
                  onSubmit={(values, { setSubmitting }) => {
                    console.log("submit");
                    mutateData.mutate(
                      {
                        matchType: values.matchType,
                        eventName: values.eventName,
                      },
                      {
                        onSuccess: (response) => {
                          console.log("save success", response);
                          handleClose();
                        },
                      }
                    );
                  }}
                >
                  {({
                    values,
                    handleChange,
                    isSubmitting,
                    /* and other goodies */
                  }) => (
                    <Form>
                      <input
                        type="text"
                        name="matchType"
                        value={values.matchType}
                        onChange={handleChange}
                      />
                      <input
                        type="text"
                        name="eventName"
                        value={values.eventName}
                        onChange={handleChange}
                      />
                      <button type="submit" disabled={isSubmitting}>
                        Submit
                      </button>
                    </Form>
                  )}
                </Formik>
              )}
            </Modal.Body>
            <Modal.Footer>
              <Button variant="secondary" onClick={handleClose}>
                Close
              </Button>
              <Button variant="primary" onClick={handleClose}>
                Save Changes
              </Button>
            </Modal.Footer>
          </Modal>
        </>
      );
    }
    
    export default App = () => {
      const [show, setShow] = useState(false);
    
      const handleClose = () => setShow(false);
      const handleShow = () => setShow(true);
      return (
        <div>
          <Button variant="primary" onClick={handleShow}>
            Launch demo modal
          </Button>
          <EditModal show={show} handleClose={handleClose} />
        </div>
      );
    };
    

    Method 2: Make the form values controlled values.

    The form only takes the initial values from initialValues props. It then loads the values that it gets from useState values. Also onChange needs to be added for each input type.

    import { useMutation, useQuery } from "@tanstack/react-query";
    import { useEffect, useState } from "react";
    import Button from "react-bootstrap/Button";
    import Modal from "react-bootstrap/Modal";
    import { faker } from "@faker-js/faker";
    import { Formik, Form } from "formik";
    
    const useEditQuery = (id, enabled) =>
      useQuery({
        queryKey: ["testKey", id],
        queryFn: () =>
          new Promise((resolve) => {
            setTimeout(
              () =>
                resolve({
                  matchType: faker.person.fullName(),
                  eventName: faker.person.fullName(),
                }),
              1000
            );
          }),
        enabled,
      });
    
    const useMutateExample = () =>
      useMutation({
        mutationFn: (payload) =>
          new Promise((resolve) => {
            setTimeout(
              () =>
                resolve({
                  status: "success",
                  result: payload,
                }),
              1000
            );
          }),
      });
    function EditModal({ show, handleClose, dataId }) {
      const { data } = useEditQuery(dataId, show);
      const mutateData = useMutateExample();
    
      const [matchType, setMatchType] = useState("");
      const [eventName, setEventName] = useState("");
    
      useEffect(() => {
        if (data) {
          setMatchType(data.matchType);
          setEventName(data.eventName);
        }
      }, [data]);
    
      console.log("data", data);
    
      const onChangeMatchType = (event) => {
        setMatchType(event.target.value);
      };
    
      const onChangeEventName = (event) => {
        setEventName(event.target.value);
      };
      return (
        <>
          <Modal show={show} onHide={handleClose}>
            <Modal.Header closeButton>
              <Modal.Title>Modal heading</Modal.Title>
            </Modal.Header>
            <Modal.Body>
              <Formik
                initialValues={{
                  matchType: "",
                  eventName: "",
                }}
                onSubmit={() => {
                  console.log("submit");
                  mutateData.mutate(
                    {
                      matchType,
                      eventName,
                    },
                    {
                      onSuccess: (response) => {
                        console.log("save success", response);
                        handleClose();
                      },
                    }
                  );
                }}
              >
                {({
                  isSubmitting,
                  /* and other goodies */
                }) => (
                  <Form>
                    <input
                      type="text"
                      name="matchType"
                      value={matchType}
                      onChange={onChangeMatchType}
                    />
                    <input
                      type="text"
                      name="eventName"
                      value={eventName}
                      onChange={onChangeEventName}
                    />
                    <button type="submit" disabled={isSubmitting}>
                      Submit
                    </button>
                  </Form>
                )}
              </Formik>
            </Modal.Body>
            <Modal.Footer>
              <Button variant="secondary" onClick={handleClose}>
                Close
              </Button>
              <Button variant="primary" onClick={handleClose}>
                Save Changes
              </Button>
            </Modal.Footer>
          </Modal>
        </>
      );
    }
    
    export default App = () => {
      const [show, setShow] = useState(false);
    
      const handleClose = () => setShow(false);
      const handleShow = () => setShow(true);
      return (
        <div>
          <Button variant="primary" onClick={handleShow}>
            Launch demo modal
          </Button>
          <EditModal show={show} handleClose={handleClose} />
        </div>
      );
    };
    

    You can check both the methods here: https://codesandbox.io/p/sandbox/react-test-forked-f9d3cl

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search