Search code examples
reactjsarraysformsindexingformik

Issue with index in a Formik FieldArray


I've been facing an issue which I don't understand on a <FieldArray> of a Formik component.

I have an array of addresses from which I would like to select or remove as many addresses as I want to another array in order to send a group mail. It's like I'm adding or removing data from a contact list.

Adding an address works well, it's removing an address which causes problems: Sometimes it doesn't remove the address I want, sometimes it removes another one...I think it has to do with the index value which I use.

Here is my code:

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; index: number }[];
}

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);
    // if (user && provider && signer) {
    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);
    }
    // } else {
    //   openConnectModal();
    // }
  };

  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, index) => {
                          const handleAddContact = () => {
                            arrayHelpers.insert(index, {
                              address: contact.owner,
                              index,
                            });
                            console.log(values.contacts);
                          };
                          const handleRemoveContact = () => {
                            arrayHelpers.remove(index);
                          };
                          const addressAlreadyAdded = values.contacts
                            .map(contact => contact.address)
                            .includes(contact.owner);
                          return (
                            <div
                              key={index}
                              className={`text-gray-400 ${addressAlreadyAdded ? 'hidden' : ''}`}>
                              {contact.owner}
                              <button
                                type='button'
                                onClick={handleRemoveContact} // remove a friend from the list
                              >
                                -
                              </button>
                              <button
                                type='button'
                                onClick={handleAddContact} // insert an empty string at a position
                              >
                                +
                              </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(contact => (
                          <div key={contact.index} className={'text-gray-400'}>
                            {contact.address}
                            <button
                              type='button'
                              onClick={() => {
                                arrayHelpers.remove(contact.index);
                                console.log(values.contacts);
                              }}>
                              -
                            </button>
                          </div>
                        ))
                      ) : (
                        <p>No Contacts</p>
                      )}
                    </div>
                  </label>
                </div>
              )}
            />

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


 const contactList: Contact[] = [
    {
      owner: 'Marc',
      address: 'address1',
      accessGrantTimestamp: '65164614646',
    },
    {
      owner: 'Bob',
      address: 'address2',
      accessGrantTimestamp: '65164614646',
    },
    {
      owner: 'Duaine',
      address: 'address3',
      accessGrantTimestamp: '65164614646',
    },
]

Thanks !

I try to add addresses from one array to the other back and forth:


Solution

  • IDK why I complicated stuff with indexes, just had to use the integrated one.

    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: 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);
        // if (user && provider && signer) {
        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);
        }
        // } else {
        //   openConnectModal();
        // }
      };
    
      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, index) => {
                              const addressAlreadyAdded = values.contacts.includes(contact.owner);
                              return (
                                <div
                                  key={index}
                                  className={`text-gray-400 ${addressAlreadyAdded ? 'hidden' : ''}`}>
                                  {contact.owner}
                                  <button
                                    type='button'
                                    onClick={() => arrayHelpers.remove(index)} // remove a friend from the list
                                  >
                                    -
                                  </button>
                                  <button
                                    type='button'
                                    onClick={() => arrayHelpers.insert(index, contact.owner)}>
                                    +
                                  </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((contact, index) => (
                              <div key={index} className={'text-gray-400'}>
                                {contact}
                                <button type='button' onClick={() => arrayHelpers.remove(index)}>
                                  -
                                </button>
                              </div>
                            ))
                          ) : (
                            <p>No Contacts</p>
                          )}
                        </div>
                      </label>
                    </div>
                  )}
                />
    
                <SubmitButton isSubmitting={isSubmitting} label='Send' />
              </div>
            </Form>
          )}
        </Formik>
      );
    };