formaat-design / reshaped

Community repository for storing examples, reporting issues and tracking roadmap
https://reshaped.so
97 stars 3 forks source link

Input textfield focus issue with formik errors #176

Closed Madhavkabra closed 9 months ago

Madhavkabra commented 9 months ago

When we are using Formik with reshaped text fields, and get an error, the component rerenders and focus goes to the first available element in parent container (in our case, a button). Instead it should change focus and to component that has errors.

Modal in which the form component is used -

import UnionIcon from '../Icons/Union';
import { Address } from '../../fqlx-generated/typedefs';
import AddressForm from './AddressForm';
import { useFormik } from 'formik';
import { addressModalValidationSchema } from './validationSchema';

function titleCase(text: string) {
  return text
    .toLowerCase()
    .split(' ')
    .map(function (word) {
      return word.charAt(0).toUpperCase() + word.slice(1);
    })
    .join(' ');
}

interface AddressModalProps {
  key?: string;
  active: boolean;
  deactivate: () => void;
  initialAddress: Address;
  formSubmit: (formAddress: Address) => void;
}

const AddressModal = ({
  key,
  active,
  deactivate,
  initialAddress,
  formSubmit,
}: AddressModalProps) => {
  const { values, handleChange, handleSubmit, errors, touched } = useFormik({
    validationSchema: addressModalValidationSchema,
    initialValues: { address: initialAddress },
    onSubmit: (values) => formSubmit(values.address),
  });

  return (
    <Modal
      active={active}
      onClose={deactivate}
      padding={6}
      className='!pb-0 !cursor-auto'
    >
      <View width='100%'>
        <View direction='row' className='!justify-between'>
          <Text variant='featured-3' weight='bold'>
            {titleCase(initialAddress.type ?? '')} Address
          </Text>
          <Button
            size={'medium'}
            variant={'ghost'}
            icon={<UnionIcon />}
            onClick={deactivate}
          />
        </View>

        <form onSubmit={handleSubmit}>
          <View paddingTop={11} paddingBottom={9}>
            <AddressForm
              values={values.address}
              addressType='address'
              errors={errors.address}
              touched={touched.address}
              onChange={handleChange}
            />
          </View>
          <View
            width='100%'
            direction='row'
            justify='end'
            paddingBlock={3}
            gap={2}
          >
            <Button
              variant='outline'
              color='neutral'
              size='medium'
              onClick={deactivate}
            >
              Cancel
            </Button>
            <Button type='submit' variant='solid' color='primary' size='medium'>
              Save
            </Button>
          </View>
        </form>
      </View>
    </Modal>
  );
};

export default AddressModal;

Form Component that has textfields


import { View, TextField, Checkbox, Text, Hidden } from 'reshaped';
import FormHelperText from '../FormHelperText';
import { Address } from '../../fqlx-generated/typedefs';

interface AddressFormProps {
  values: Address;
  addressType: string;
  isBillingSameAsShippingAddress?: boolean;
  errors: any;
  touched: any;
  onChange: (event: any) => void;
}

const AddressForm = ({
  values,
  addressType,
  isBillingSameAsShippingAddress,
  errors,
  touched,
  onChange,
}: AddressFormProps) => {
  return (
    <View gap={6} direction='row'>
      <View.Item columns={12}>
        <Text
          variant={'body-3'}
          weight={'medium'}
          className={`${addressType}.pb-x1`}
        >
          Full Name
        </Text>
        <TextField
          name={`${addressType}.name`}
          variant={'outline'}
          placeholder={'John Doe Jr. III'}
          className={'!rounded-medium'}
          value={values.name}
          onChange={({ event }) => {
            onChange(event);
          }}
        />
        {errors?.name && touched?.name && (
          <FormHelperText
            message={'This field is required'}
            variant={'error'}
          />
        )}
      </View.Item>
      <View.Item columns={12}>
        <Text
          variant={'body-3'}
          weight={'medium'}
          className={`${addressType}.pb-x1`}
        >
          Street Address
        </Text>
        <TextField
          name={`${addressType}.street`}
          variant={'outline'}
          placeholder={'Mozart 3 strasse'}
          className={'!rounded-medium'}
          value={values.street}
          onChange={({ event }) => {
            onChange(event);
          }}
        />
        {errors?.street && touched?.street && (
          <FormHelperText
            message={'This field is required'}
            variant={'error'}
          />
        )}
      </View.Item>
      <View.Item columns={12}>
        <Text
          variant={'body-3'}
          weight={'medium'}
          className={`${addressType}.pb-x1`}
        >
          {'Building number, apartment, suite etc. (optional)'}
        </Text>
        <TextField
          name={`${addressType}.streetNo`}
          variant={'outline'}
          placeholder={'1A...'}
          className={'!rounded-medium'}
          value={values.streetNo}
          onChange={({ event }) => {
            onChange(event);
          }}
        />
        {errors?.streetNo && touched?.streetNo && (
          <FormHelperText
            message={'This field is required'}
            variant={'error'}
          />
        )}
      </View.Item>
      <View.Item columns={{ s: 12, l: 6 }}>
        <Text
          variant={'body-3'}
          weight={'medium'}
          className={`${addressType}.pb-x1`}
        >
          Postal Code
        </Text>
        <TextField
          name={`${addressType}.zip`}
          variant={'outline'}
          placeholder={'XXX XXX'}
          className={'!rounded-medium'}
          value={values.zip}
          onChange={({ event }) => {
            onChange(event);
          }}
        />
        {errors?.zip && touched?.zip && (
          <FormHelperText
            message={'This field is required'}
            variant={'error'}
          />
        )}
      </View.Item>
      <View.Item columns={{ s: 12, l: 6 }}>
        <Text
          variant={'body-3'}
          weight={'medium'}
          className={`${addressType}.pb-x1`}
        >
          City
        </Text>
        <TextField
          name={`${addressType}.city`}
          variant={'outline'}
          placeholder={'Berlin'}
          className={'!rounded-medium'}
          value={values.city}
          onChange={({ event }) => {
            onChange(event);
          }}
        />
        {errors?.city && touched?.city && (
          <FormHelperText
            message={'This field is required'}
            variant={'error'}
          />
        )}
      </View.Item>
      <View.Item columns={{ s: 12, l: 6 }}>
        <Text
          variant={'body-3'}
          weight={'medium'}
          className={`${addressType}.pb-x1`}
        >
          State
        </Text>
        <TextField
          name={`${addressType}.state`}
          variant={'outline'}
          placeholder={'Bavaria'}
          className={'!rounded-medium'}
          value={values.state}
          onChange={({ event }) => {
            onChange(event);
          }}
        />
        {errors?.state && touched?.state && (
          <FormHelperText
            message={'This field is required'}
            variant={'error'}
          />
        )}
      </View.Item>
      <View.Item columns={{ s: 12, l: 6 }}>
        <Text
          variant={'body-3'}
          weight={'medium'}
          className={`${addressType}.pb-x1`}
        >
          Country
        </Text>
        <TextField
          name={`${addressType}.country`}
          variant={'outline'}
          className={'!rounded-medium'}
          value={values.country}
          onChange={({ event }) => {
            onChange(event);
          }}
        />
        {errors?.country && touched?.country && (
          <FormHelperText
            message={'This field is required'}
            variant={'error'}
          />
        )}
      </View.Item>
      <Hidden hide={isBillingSameAsShippingAddress === undefined}>
        <View.Item columns={12}>
          <Checkbox
            name={`isBillingSameAsShippingAddress`}
            value={`${isBillingSameAsShippingAddress}`}
            checked={isBillingSameAsShippingAddress}
            onChange={({ event }) => onChange(event)}
          >
            My billing address is same as shipping address
          </Checkbox>
        </View.Item>
      </Hidden>
    </View>
  );
};

export default AddressForm;
mmailaender commented 9 months ago

This is also happening for me on mobile on the reshaped homepage. So probably not a specific issue to us, instead of a general reshaped bug.

blvdmitry commented 9 months ago

Fixed it in 2.4.2, can you check if it works fine now?

Madhavkabra commented 9 months ago

Hey @blvdmitry , The jumping bug is gone in the 2.4.2. Thanks :D

Is it possible to autofocus on the first error field once the error appears?

blvdmitry commented 9 months ago

Since we don't have information about which libraries you integrate it with, we can't control it automatically. But you can get access to the input elements through inputAttributes={{ ref }} and use the refs to focus the correct input