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

Formik组件拖拽排序后状态不更新问题求助

Hey there! Let's tackle those two Formik + react-sortly sync issues you're facing. Both problems boil down to not connecting your local component state with Formik's global state properly—let's fix that step by step.

Problem Breakdown

  1. Drag-sorting parents doesn't update Formik values: Your PSortableTree uses its own local useState to track parent nodes, so drag changes stay trapped in that component instead of updating Formik's state.
  2. Deleting children doesn't refresh the drag area: KSortableTree stores child items in local state, so when you delete a child via Formik's helpers, the drag area never gets the latest data from Formik.

Fixed Code Implementation

Here's the revised code that resolves both issues:

First, update your imports to include Formik's context hook:

import React from "react";
import ReactDOM from "react-dom";
import { Formik, FieldArray, useFormikContext } from "formik"; // Add useFormikContext
import { Button, Form } from "antd";
import { DndProvider } from "react-dnd";
import HTML5Backend from "react-dnd-html5-backend";
import Sortly, { ContextProvider, useDrag, useDrop } from "react-sortly";

const FormItem = Form.Item;

Rewrite Item Renderers to Use Formik Context

const ItemRenderer = props => {
  const { index, data: { text, kids } } = props;
  const { setFieldValue, values } = useFormikContext(); // Access Formik state/methods
  const [, drag] = useDrag();
  const [, drop] = useDrop();

  return (
    <FieldArray name="family.parents" render={arrayHelpers => (
      <div ref={drop} style={{ width: "40vw" }} key={index}>
        <div ref={drag} style={{ padding: "0.5rem 1rem", marginBottom: ".5rem", backgroundColor: "green", cursor: "move", color: "white", width: "320px" }} >
          {text}
          <FormItem style={{ display: "inline-block" }}>
            <span className="parent-utility">
              <Button 
                className="ant-btn" 
                shape="circle" 
                size="small" 
                style={{ color: "#ffa500" }} 
                onClick={() => arrayHelpers.remove(index)}
              >
                Delete
              </Button>
            </span>
          </FormItem>
        </div>
        <KSortableTree 
          position={index} 
          values={values} 
          setFieldValue={setFieldValue} 
        />
      </div>
    )} />
  );
};

const ItemRendererA = props => {
  const { index, position, data: { text } } = props;
  const { setFieldValue } = useFormikContext();
  const [, drag] = useDrag();
  const [, drop] = useDrop();

  return (
    <FieldArray name={`family.parents[${position}].kids`} render={arrayHelpers => (
      <>
        <div ref={drop} style={{ width: "40vw" }}>
          <div ref={drag} style={{ border: "1px dashed #70B5DC", padding: "0.5rem 1rem", marginBottom: ".5rem", marginLeft: "3rem", backgroundColor: "white", cursor: "move", color: "#70B5DC", width: "17rem" }} >
            {text}
            <FormItem style={{ display: "inline-block" }}>
              <span className="answer-utility">
                <Button 
                  className="ant-btn" 
                  shape="circle" 
                  size="small" 
                  style={{ color: "#ffa500" }} 
                  onClick={() => arrayHelpers.remove(index)}
                >
                  Delete
                </Button>
              </span>
            </FormItem>
          </div>
        </div>
      </>
    )} />
  );
};

Remove Local State from Sortable Trees

const PSortableTree = ({ parentTree, setFieldValue }) => {
  const handleChange = newItems => {
    // Sync drag-sorted parents directly to Formik state
    setFieldValue("family.parents", newItems);
  };

  return (
    <Sortly items={parentTree} onChange={handleChange}>
      {props => <ItemRenderer {...props} />}
    </Sortly>
  );
};

const KSortableTree = ({ position, values, setFieldValue }) => {
  // Pull latest child items directly from Formik's state
  const itemsA = values.family.parents[position]?.kids || [];

  const handleChangeA = newItemsA => {
    // Sync drag-sorted children to the correct parent in Formik state
    setFieldValue(`family.parents[${position}].kids`, newItemsA);
  };

  return (
    <Sortly items={itemsA} onChange={handleChangeA}>
      {props => <ItemRendererA {...props} position={position} />}
    </Sortly>
  );
};

Update FormDetail to Pass Formik Methods

function FormDetail({ values, handleSubmit, setFieldValue }) {
  return (
    <form onSubmit={handleSubmit}>
      <h3>Family Tree</h3>
      <DndProvider backend={HTML5Backend}>
        <ContextProvider>
          <PSortableTree 
            parentTree={values.family.parents} 
            setFieldValue={setFieldValue} 
          />
        </ContextProvider>
      </DndProvider>
      <div style={{ padding: 16, backgroundColor: "lightgrey", borderRadius: "8px", marginTop: "20px", maxWidth: "100vw" }} >
        {JSON.stringify(values)}
      </div>
    </form>
  );
};

Keep Your App Component as-is

function App() {
  return (
    <Formik 
      enableReinitialize 
      initialValues={{ 
        family: { 
          id: "2da80396", 
          parents: [ 
            { depth: 0, id: "01a3b77c", kids: [ { depth: "1", id: "01a3b77d", text: "A", correct: true }, { depth: "1", id: "02fd6f62", text: "C", correct: false } ], text: "Parent 1" }, 
            { depth: 0, id: "773a4170", kids: [ { depth: "1", id: "773a4171", text: "A", correct: false }, { depth: "1", id: "773a4172", text: "B", correct: true }, { depth: "1", id: "773a4173", text: "C", correct: false } ], text: "Parent 2" }, 
            { depth: 0, id: "a3652131", kids: [ { depth: "1", id: "1c081c33", text: "G", correct: false }, { depth: "1", id: "a3654842", text: "A", correct: true }, { depth: "1", id: "a3654843", text: "B", correct: false } ], text: "Parent 3" }, 
            { depth: 0, id: "a3654845", kids: [ { depth: "1", id: "a3654846", text: "A", correct: true }, { depth: "1", id: "a3654847", text: "B", correct: false } ], text: "Parent 4" }, 
            { depth: 0, id: "eb3dc4d9", kids: [ { depth: "1", id: "2103b3bc", text: "D", correct: true }, { depth: "1", id: "28650f35", text: "A", correct: false }, { depth: "1", id: "699584c2", text: "B", correct: false }, { depth: "1", id: "a9edd5c4", text: "C", correct: false } ], text: "Parent 5" } 
          ], 
          text: "Heading" 
        } 
      }} 
      onSubmit={values => alert(JSON.stringify(values))} 
      component={FormDetail} 
    />
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Key Fixes Explained

  1. Eliminated local state: We now pull all tree data directly from Formik's values, so the drag area always reflects the latest form state.
  2. Sync drag changes to Formik: When Sortly detects a drag reorder, we use setFieldValue to update Formik's state immediately—no more disconnected data.
  3. Used useFormikContext: This hook lets us access Formik's state and methods anywhere in the component tree without messy prop drilling.
  4. Auto-refresh on delete: Since the drag area is tied directly to Formik's values, deleting a node via arrayHelpers.remove automatically updates the UI.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 07:57:45