skip to Main Content

I have a Field in a Formik which needs a dynamic validation Schema: When a user selects a payment token, the minimum payment value must be dynamically changed for another input field.
I used a state value and the "onChange" listener from the Field, and it works, except the displayed value

{token.symbol}

is not rendered any more.

As if overriding the "onChange()" field method had deactivated the Formik selection of this field.

Any idea what’s happening ?

My code:

import { ErrorMessage, Field, Form, Formik } from 'formik';
import { SetStateAction, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import * as Yup from 'yup';
import SubmitButton from './SubmitButton';
import useAllowedTokens from '../../hooks/useAllowedTokens';

interface IFormValues {
  rateToken: string;
  rateAmount: number;
}

const initialValues: IFormValues = {
  rateToken: '',
  rateAmount: 0,
};

function ServiceForm() {
  const navigate = useNavigate();
  const allowedTokenList = useAllowedTokens();
  const [selectedToken, setSelectedToken] = useState('');

  const validationSchema = Yup.object({
    rateToken: Yup.string().required('Please select a payment token'),
    rateAmount: Yup.number()
      .required('Please provide an amount for your service')
      .when('rateToken', {
        is: (rateToken: string) => rateToken !== '',
        then: schema =>
          schema.moreThan(
            allowedTokenList.find(token => token.address === selectedToken)
              ?.minimumTransactionAmount || 0,
            `Amount must be greater than ${
              allowedTokenList.find(token => token.address === selectedToken)
                ?.minimumTransactionAmount || 0
            }`,
          ),
      }),
  });

  // const onSubmit = async (values: IFormValues) => {
  //   <...>
  // }

  return (
    <Formik initialValues={initialValues} onSubmit={onSubmit} validationSchema={validationSchema}>
      {({ isSubmitting }) => (
        <Form>
          <div className='grid grid-cols-1 gap-6 border border-gray-200 rounded-md p-8'>
            <div className='flex'>
              <label className='block flex-1 mr-4'>
                <span className='text-gray-700'>Amount</span>
                <Field
                  type='number'
                  id='rateAmount'
                  name='rateAmount'
                  className='mt-1 mb-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50'
                  placeholder=''
                />
                <span className='text-red-500 mt-2'>
                  <ErrorMessage name='rateAmount' />
                </span>
              </label>

              <label className='block'>
                <span className='text-gray-700'>Token</span>
                <Field
                  component='select'
                  id='rateToken'
                  name='rateToken'
                  className='mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50'
                  placeholder=''
                  onChange={(event: { target: { value: SetStateAction<string> } }) => {
                    setSelectedToken(event.target.value);
                  }}>
                  <option value=''>Select a token</option>
                  {allowedTokenList.map((token, index) => (
                    <option key={index} value={token.address}>
                      {token.symbol}
                    </option>
                  ))}
                </Field>
                <span className='text-red-500'>
                  <ErrorMessage name='rateToken' />
                </span>
              </label>
            </div>

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

export default ServiceForm;

Tried: Overriding onChange() field method

Expected: The selected <option>’s value (token) should be updated in the form’s input data, but is not.

2

Answers


  1. Chosen as BEST ANSWER

    I had found another way, by extracting a child formik component:

        const RateTokenField = () => {
        const formikProps = useFormikContext();
    
        const handleChange = async (event: { target: { value: SetStateAction<string> } }) => {
          const token = allowedTokenList.find(token => token.address === event.target.value);
          setSelectedToken(token);
          formikProps.setFieldValue('rateToken', event.target.value);
        };
    
        return (
          <label className='block'>
            <span className='text-gray-700'>Token</span>
            <Field
              component='select'
              id='rateToken'
              name='rateToken'
              className='mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50'
              placeholder=''
              onChange={handleChange}>
              <option value=''>Select a token</option>
              {allowedTokenList.map((token, index) => (
                <option key={index} value={token.address}>
                  {token.symbol}
                </option>
              ))}
            </Field>
            <span className='text-red-500'>
              <ErrorMessage name='rateToken' />
            </span>
          </label>
        );
      };
    

    Same idea.


  2. To set a field value based on another field changes, you have to use setFieldValue from Formik, Here is the right way to do it

    The selected ‘s value (token) should be updated in the form’s input data, but is not.

    You have this issue because you didn’t set the value using the formmik setFieldValue

    You have to set the value of the current field then set the value calculated for the other field

     {({ isSubmitting, setFieldValue }) => (
    
       ...
    
       onChange={(event: { target: { value: SetStateAction<string> } }) => {
        setFieldValue('rateToken', value)
        // select amount value based on token selection
        // calculation code here
        setFieldValue('rateAmount', calculatedAmount)
       }}>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search