如何用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
相关产品推荐
相关产品推荐

