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

如何用Formik在React+DRF中高效提交含多卡片的闪卡组表单?

实现方案:用两个Formik组件完成Deck+批量Card提交

核心思路

用第一个Formik处理Deck的基础信息提交,拿到返回的deck_id后,再用第二个Formik把所有卡片数据批量提交,总共只触发2次API请求,彻底解决n+1次请求的效率问题。

具体代码实现

1. 组件基础结构与状态管理

首先在组件里用useState存储Deck创建成功后的ID,作为卡片关联的标识:

import { useState } from 'react';
import { Formik, Form, Field, FieldArray } from 'formik';
import axios from 'axios';

const CreateDeckPage = () => {
  const [createdDeckId, setCreatedDeckId] = useState(null);

  // 提交Deck表单的逻辑
  const handleDeckSubmit = async (values, { setSubmitting, resetForm }) => {
    try {
      const res = await axios.post('/api/decks/', {
        title: values.title,
        description: values.description,
        directory_id: values.directory_id
      });
      setCreatedDeckId(res.data.id); // 保存后端返回的deck_id
      resetForm();
    } catch (err) {
      console.error('创建卡组失败:', err);
    } finally {
      setSubmitting(false);
    }
  };

  // 批量提交所有卡片的逻辑
  const handleCardsSubmit = async (values, { setSubmitting, resetForm }) => {
    if (!createdDeckId) return;

    // 给每个卡片添加上关联的deck_id
    const cardsWithDeckId = values.cards.map(card => ({
      ...card,
      deck_id: createdDeckId
    }));

    try {
      // 一次性提交所有卡片到后端
      await axios.post('/api/cards/', cardsWithDeckId, {
        headers: { 'Content-Type': 'application/json' }
      });
      resetForm();
      setCreatedDeckId(null); // 重置状态,方便创建新卡组
    } catch (err) {
      console.error('批量创建卡片失败:', err);
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <div className="create-deck-container">
      {/* 第一个Formik:Deck信息表单 */}
      <h2>创建闪卡组</h2>
      <Formik
        initialValues={{ title: '', description: '', directory_id: '' }}
        onSubmit={handleDeckSubmit}
      >
        {({ isSubmitting }) => (
          <Form>
            <div>
              <label>卡组标题</label>
              <Field type="text" name="title" required />
            </div>
            <div>
              <label>卡组描述</label>
              <Field as="textarea" name="description" />
            </div>
            <div>
              <label>目录ID</label>
              <Field type="number" name="directory_id" required />
            </div>
            <button type="submit" disabled={isSubmitting}>创建卡组</button>
          </Form>
        )}
      </Formik>

      {/* 第二个Formik:批量卡片表单,仅Deck创建成功后显示 */}
      {createdDeckId && (
        <>
          <h2>添加闪卡(支持批量添加)</h2>
          <Formik
            initialValues={{ cards: [{ side1: '', side2: '' }] }}
            onSubmit={handleCardsSubmit}
          >
            {({ values, isSubmitting }) => (
              <Form>
                {/* 用FieldArray动态管理多个卡片输入框 */}
                <FieldArray name="cards">
                  {({ push, remove }) => (
                    <>
                      {values.cards.map((card, index) => (
                        <div key={index} className="card-form">
                          <div>
                            <label>正面内容</label>
                            <Field type="text" name={`cards[${index}].side1`} required />
                          </div>
                          <div>
                            <label>反面内容</label>
                            <Field type="text" name={`cards[${index}].side2`} required />
                          </div>
                          {index > 0 && (
                            <button type="button" onClick={() => remove(index)}>删除卡片</button>
                          )}
                        </div>
                      ))}
                      <button type="button" onClick={() => push({ side1: '', side2: '' })}>添加新卡片</button>
                    </>
                  )}
                </FieldArray>
                <button type="submit" disabled={isSubmitting}>保存所有卡片</button>
              </Form>
            )}
          </Formik>
        </>
      )}
    </div>
  );
};

export default CreateDeckPage;

后端DRF适配说明

要支持卡片批量创建,需要在Card的视图中处理数组格式的请求数据:

# cards/views.py
from rest_framework import viewsets, status
from rest_framework.response import Response
from .models import Card
from .serializers import CardSerializer

class CardViewSet(viewsets.ModelViewSet):
    queryset = Card.objects.all()
    serializer_class = CardSerializer

    def create(self, request, *args, **kwargs):
        # 判断是否为批量创建请求
        if isinstance(request.data, list):
            serializer = self.get_serializer(data=request.data, many=True)
        else:
            serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

关键细节

  • 用FieldArray组件自动维护多卡片的表单状态,无需手动处理数组增删的逻辑。
  • 只有Deck创建成功后才显示卡片表单,确保卡片能正确关联到目标Deck。
  • 批量提交卡片时统一添加deck_id,后端可直接完成关联存储。

内容的提问来源于stack exchange,提问作者Tucan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 00:20:51