You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Formik+React DatePicker中显示MongoDB数据库日期

问题:Formik + React DatePicker无法显示MongoDB中的日期

我用Formik和自定义React DatePicker组件构建数据编辑表单,但没法把MongoDB里的日期显示在日期选择器输入框里。尝试过这些方法但都有问题:

  • 在useEffect里用moment把日期格式化为M/D/YYYY(和选择后的格式一致),但输入框是空的;
  • 改成M-D-YYYY格式能显示,但点击输入框会触发时间格式错误;
  • 直接在Formik初始值里用moment对象,会触发“Maximum update depth exceeded”无限循环错误。

以下是相关代码:

CustomDatePicker组件

import { useField } from "formik";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";

const CustomDatePicker = ({ label, name = "" }) => {
  const [field, meta, helpers] = useField(name);

  const { value } = meta;
  const { setValue } = helpers;

  return (
    <>
      <label>{label}</label>
      <DatePicker
        {...field}
        selected={value}
        onChange={(date) => setValue(date)}
      />
    </>
  );
};

export default CustomDatePicker;

调用代码

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

完整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 "../countries-codes.json";
// icons
import { BsFillQuestionDiamondFill } from "react-icons/bs";
import {
  useCreateReportMutation,
  useGetReportByIdQuery,
} 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";

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("");

  useEffect(() => {
    if (myReport) {
      setReportType(myReport?.reportType);
      setMatchType(myReport?.matchType);
      setEventName(myReport?.eventName);
      setEventStartDate(moment(myReport?.eventStartDate).format("M/D/YYYY"));
    }
  }, [myReport]);
  const onSubmit = async (values, action) => {};

  const windowSize = useRef([window.innerWidth, window.innerHeight]);
  const tooltipWidth = windowSize.current[0] > 500 ? "50vw" : "90vw";

  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
            initialValues={{
              athlete: "",
              reportType: "",
              matchType: "",
              eventName: "",
              eventStartDate: eventStartDate,
              eventEndDate: "",
              opponentName: "",
              opponentClub: "",
              opponentCountry: "",
              opponentRank: "",
              opponentHandedness: "",
              opponentAttacks: "",
              opponentAttackNotes: "",
              athleteAttachs: "",
              athleteAttackNotes: "",
              result: "",
              score: "",
              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={myReport?.reportType === "Result"}
                      />
                      <CustomRadio
                        label="Scouting"
                        value="Scouting"
                        name="reportType"
                        checked={myReport?.reportType === "Scouting"}
                        placeholder="Scouting"
                      />
                      <CustomRadio
                        label="Both"
                        value="Both"
                        name="reportType"
                        placeholder="Both"
                        checked={myReport?.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"
                      value={eventName}
                    />
                  </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="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..."
                    >
                      <option value="">Select country...</option>
                      <option value="USA">United States</option>
                      <option value="">----------</option>
                      {Countries.sort().map((country) => (
                        <option
                          key={country.iso3_code}
                          value={country.iso3_code}
                        >
                          {country.label_en}
                        </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="athleteAttachs"
                      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;

解决方案

核心问题分析

React DatePicker必须接收Date对象或moment对象作为selected属性的值,你之前把日期转成字符串格式,导致组件无法识别;直接用moment对象时,因为对象引用变化触发Formik无限重渲染。

步骤1:修复CustomDatePicker组件

让组件自动处理字符串到Date对象的转换,同时统一显示格式:

import { useField } from "formik";
import DatePicker from "react-datepicker";
import moment from "moment";
import "react-datepicker/dist/react-datepicker.css";

const CustomDatePicker = ({ label, name = "" }) => {
  const [field, meta, helpers] = useField(name);
  const { value } = meta;
  const { setValue } = helpers;

  // 解析字符串/日期为Date对象
  const parseDate = (val) => {
    if (!val) return null;
    if (val instanceof Date || moment.isMoment(val)) return val;
    // 兼容多种日期格式
    const parsed = moment(val, ["M/D/YYYY", "M-D-YYYY", "YYYY-MM-DD"], true);
    return parsed.isValid() ? parsed.toDate() : null;
  };

  return (
    <>
      <label>{label}</label>
      <DatePicker
        {...field}
        selected={parseDate(value)}
        onChange={(date) => setValue(date)}
        dateFormat="M/d/YYYY" // 统一显示格式
      />
    </>
  );
};

export default CustomDatePicker;

步骤2:优化EditReportModal的状态管理

去掉冗余本地状态,让Formik直接从接口数据生成初始值,避免状态不同步:

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

  // 直接从接口数据生成Formik初始值
  const getInitialValues = () => {
    if (!myReport) {
      return {
        athlete: "",
        reportType: "",
        matchType: "",
        eventName: "",
        eventStartDate: null,
        eventEndDate: null,
        opponentName: "",
        opponentClub: "",
        opponentCountry: "",
        opponentRank: "",
        opponentHandedness: "",
        opponentAttacks: "",
        opponentAttackNotes: "",
        athleteAttachs: "",
        athleteAttackNotes: "",
        result: "",
        score: "",
        isPublic: "",
      };
    }
    return {
      athlete: myReport.athlete || "",
      reportType: myReport.reportType || "",
      matchType: myReport.matchType || "",
      eventName: myReport.eventName || "",
      // 将MongoDB日期转为Date对象
      eventStartDate: myReport.eventStartDate ? new Date(myReport.eventStartDate) : null,
      eventEndDate: myReport.eventEndDate ? new Date(myReport.eventEndDate) : null,
      opponentName: myReport.opponentName || "",
      opponentClub: myReport.opponentClub || "",
      opponentCountry: myReport.opponentCountry || "",
      opponentRank: myReport.opponentRank || "",
      opponentHandedness: myReport.opponentHandedness || "",
      opponentAttacks: myReport.opponentAttacks || "",
      opponentAttackNotes: myReport.opponentAttackNotes || "",
      athleteAttachs: myReport.athleteAttachs || "",
      athleteAttackNotes: myReport.athleteAttackNotes || "",
      result: myReport.result || "",
      score: myReport.score || "",
      isPublic: myReport.isPublic || "",
    };
  };

  const onSubmit = async (values, action) => {
    // 提交时将Date对象转为MongoDB兼容格式
    const submitValues = {
      ...values,
      eventStartDate: values.eventStartDate ? moment(values.eventStartDate).format("YYYY-MM-DD") : null,
      eventEndDate: values.eventEndDate ? moment(values.eventEndDate).format("YYYY-MM-DD") : null,
    };
    // 执行提交逻辑
    action.setSubmitting(false);
    handleClose();
    refetch();
  };

  const windowSize = useRef([window.innerWidth, window.innerHeight]);
  const tooltipWidth = windowSize.current[0] > 500 ? "50vw" : "90vw";

  return (
    <Modal size="xl" show={show} onHide={handleClose}>
      <Modal.Header closeButton>
        <Modal.Title>编辑赛事报告</Modal.Title>
      </Modal.Header>
      <Modal.Body>
        {isLoading && <Loader />}
        <FormContainer>
          <Formik
            initialValues={getInitialValues()}
            validateSchema={createReport}
            onSubmit={onSubmit}
            enableReinitialize // 关键:接口数据更新时自动重置表单
          >
            {({ isSubmitting }) => (
              <Form>
                <Row>
                  <Col className="d-flex flex-column my-2">
                    <label>
                      报告类型
                      <BsFillQuestionDiamondFill
                        data-tooltip-id="report-type-tooltip"
                        className="ms-2"
                      />
                      <Tooltip
                        id="report-type-tooltip"
                        style={{
                          width: `${tooltipWidth}`,
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 02:52:04