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

Formik FieldArray索引异常:移除联系人时误删目标项

Formik FieldArray 移除联系人异常问题修复

你的移除功能出错,核心是误用了原始联系人列表的索引来操作Formik的FieldArray数组,加上几处逻辑错误,导致索引对应混乱,出现删错项、无法删除的问题。具体问题点:

  1. 可用联系人列表里的handleRemoveContact用了contactList的原始索引调用arrayHelpers.remove(index),但这个索引和values.contacts数组的索引完全不对应,必然删错。
  2. 选中联系人列表里用contact.index(原始列表索引)作为key,移除时直接用该索引操作FieldArray,而values.contacts的元素顺序和原始列表无关,索引完全不匹配。
  3. 添加联系人时用arrayHelpers.insert(index, ...)插入到原始列表对应的位置,打乱了contacts数组的索引逻辑,进一步加剧移除混乱。
  4. 地址已添加的判断逻辑错误:用联系人名字(owner)匹配地址数组,导致判断失效。

修复步骤

1. 修正表单值结构,移除冗余索引字段

直接存储联系人的唯一标识(address)和名称(owner),去掉无用的index:

interface IFormValues {
  subject: string;
  body: string;
  contacts: { address: string; owner: string }[];
}

2. 修正添加联系人逻辑

改用push追加元素,同时修正已添加判断的匹配字段:

const handleAddContact = () => {
  arrayHelpers.push({
    address: contact.address,
    owner: contact.owner
  });
};

// 用contact.address匹配,判断是否已添加
const addressAlreadyAdded = values.contacts
  .map(item => item.address)
  .includes(contact.address);

3. 移除可用联系人里的错误移除按钮

可用联系人列表里的-按钮逻辑完全错误,直接删除即可。

4. 修正选中联系人的移除逻辑

用当前元素在values.contacts数组里的循环索引idx执行移除,同时用唯一的address作为key避免渲染异常:

values.contacts.map((contactItem, idx) => (
  <div key={contactItem.address} className={'text-gray-400'}>
    {contactItem.owner}
    <button
      type='button'
      onClick={() => {
        arrayHelpers.remove(idx);
      }}>
      -
    </button>
  </div>
))

完整修复后代码

import { Formik, Form, Field, FieldArray, ErrorMessage } from 'formik';
import SubmitButton from './SubmitButton';
import * as Yup from 'yup';
import axios from 'axios';
import { showErrorTransactionToast } from '../../utils/toast';
import { Contact } from '@iexec/web3mail';

interface IFormValues {
  subject: string;
  body: string;
  contacts: { address: string; owner: string }[];
}

const validationSchema = Yup.object({
  subject: Yup.string().required('Please provide a subject'),
  body: Yup.string().required('Please provide a body'),
  contacts: Yup.array().min(1).required('Please provide at least one contact'),
});
export const ContactListForm = ({ contactList }: { contactList: Contact[] }) => {
  const initialValues: IFormValues = {
    subject: '',
    body: '',
    contacts: [],
  };

  const onSubmit = async (
    values: IFormValues,
    { setSubmitting }: { setSubmitting: (isSubmitting: boolean) => void },
  ) => {
    console.log(values);
    try {
      setSubmitting(true);

      await axios.post('/api/iexec/send-web3mail-to-addresses', {
        subject: values.subject,
        body: values.body,
        contacts: values.contacts,
      });

      setSubmitting(false);
    } catch (error) {
      showErrorTransactionToast(error);
    }
  };

  return (
    <Formik
      initialValues={initialValues}
      enableReinitialize={true}
      onSubmit={onSubmit}
      validationSchema={validationSchema}>
      {({ isSubmitting, values }) => (
        <Form>
          <div className='grid grid-cols-1 gap-6 border border-gray-700 rounded-xl p-6 bg-endnight'>
            <label className='block'>
              <span className='text-gray-100'>Subject</span>
              <Field
                type='text'
                id='subject'
                name='subject'
                className='mt-1 mb-1 block w-full rounded-xl border border-gray-700 bg-midnight shadow-sm focus:ring-opacity-50'
                placeholder='Type your subject here...'
              />
              <span className='text-red-500'>
                <ErrorMessage name='subject' />
              </span>
            </label>

            <label className='block'>
              <span className='text-gray-100'>Body</span>
              <Field
                as='textarea'
                rows='4'
                id='body'
                name='body'
                className='mt-1 mb-1 block w-full rounded-xl border border-gray-700 bg-midnight shadow-sm focus:ring-opacity-50'
                placeholder='Type the email body here...'
              />
              <span className='text-red-500'>
                <ErrorMessage name='body' />
              </span>
            </label>

            <FieldArray
              name='contacts'
              render={arrayHelpers => (
                <div className={'flex flex-row space-x-10'}>
                  <label className='block flex-auto'>
                    <span className='text-gray-100'>Available Contacts</span>
                    <div className={'overflow-y-auto overflow-x-visible h-24'}>
                      {contactList && contactList.length > 0 ? (
                        contactList.map((contact) => {
                          const handleAddContact = () => {
                            arrayHelpers.push({
                              address: contact.address,
                              owner: contact.owner,
                            });
                          };
                          const addressAlreadyAdded = values.contacts
                            .map(item => item.address)
                            .includes(contact.address);
                          return (
                            <div
                              key={contact.address}
                              className={`text-gray-400 ${addressAlreadyAdded ? 'hidden' : ''}`}>
                              {contact.owner}
                              <button
                                type='button'
                                onClick={handleAddContact}
                              >
                                +
                              </button>
                            </div>
                          );
                        })
                      ) : (
                        <p>No Contacts</p>
                      )}
                    </div>
                    <span className='text-red-500'>
                      <ErrorMessage name='contacts' />
                    </span>
                  </label>
                  <label className='block flex-auto '>
                    <span className='text-gray-100'>Selected Contacts</span>
                    <div className={'overflow-y-auto overflow-x-visible w-auto h-24'}>
                      {values.contacts.length > 0 ? (
                        values.contacts.map((contactItem, idx) => (
                          <div key={contactItem.address} className={'text-gray-400'}>
                            {contactItem.owner}
                            <button
                              type='button'
                              onClick={() => {
                                arrayHelpers.remove(idx);
                              }}>
                              -
                            </button>
                          </div>
                        ))
                      ) : (
                        <p>No Contacts</p>
                      )}
                    </div>
                  </label>
                </div>
              )}
            />

            <SubmitButton isSubmitting={isSubmitting} label='Send' />
          </div>
        </Form>
      )}
    </Formik>
  );
};

额外说明

  • 所有列表项使用唯一的address作为key,避免索引变化导致的React渲染异常
  • 移除操作直接使用元素在values.contacts中的真实索引,确保删除目标正确
  • 修正了已添加判断的逻辑错误,现在能正确隐藏已选中的联系人

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 08:00:00