#StackBounty: #reactjs #validation #input #formik How to get the length of material UI input filed in React JS?

Bounty: 50

I am trying to enable the button when the material input text length is more than 0.

This is a material input field component.

const MaterialInput = ({
    name,
    id = name,
    select,
    options = [],
    type,
    label,
    shrink,
    formik,
    disabled,
    handleClick,
}) => {
    return (
        <TextField
            type={type}
            name={name}
            label={label}
            select={select}
            autoComplete='off'
            variant='outlined'
            disabled={disabled}
            className='material-input'
            value={get(formik.values, name, '')}
            onBlur={formik.handleBlur}
            onChange={formik.handleChange}
            helperText={get(formik.touched, name) && get(formik.errors, name)}
            error={get(formik.touched, name) && Boolean(get(formik.errors, name))}
            InputLabelProps={{ shrink }}
        >
            {options.map((item) => (
                <MenuItem
                    onClick={handleClick}
                    className='text-capitalize'
                    value={item.value}
                    key={item.value}
                    disabled={item.disabled || false}
                >
                    {item.label}
                </MenuItem>
            ))}
        </TextField>
    );
};

This is how I am using it in other components.

aComponent.js

                <MaterialInput name={inputName} label={label} formik={typeofForm} />

How to Enable button when the length of text field is more than 0.


Get this bounty!!!

#StackBounty: #reactjs #react-native #react-redux #formik How can I load my initialvalue in my form input?

Bounty: 50

I need to create a modify feature on a value. I have the input setup inside a modal but for some reason I can’t seem to pass on the value to the initialValues.

My code starts with ActivityAreas

<Datatable
  ref={this.datatable}
  rows={this.props.activityAreas}
  update={this.props.updateTable}
  headers={[
    {
      key: "name",
      title: () => t("entities.activityAreas.name"),
    },
    {
      key: "action",
      title: () => t("entities.activityAreas.action"),
      render: (_, row) => (
        <Flex>
          <ClickableLink onClick={() => this.openModalDestroy(row)}>
            {t("entities.activityAreas.destroy")}
          </ClickableLink>
          <ClickableLink onClick={() => this.openModalRename(row)}>
            {t("entities.activityAreas.rename")}
          </ClickableLink>
        </Flex>
      ),
      props: { align: "right" },
    },
  ]}
  type={ACTIVITY_AREAS}
/>

The line that says
this.openModalRename(row)

contains the actual line object with name and id. openModalRename looks like this right now

openModalRename = (row) => {
    let modalProps = this.modalProps;
    modalProps.row=row;
    this.props.setModal(RenameActivityArea, modalProps, { width: '500px' })
}

It sends the data to the RenameActivityArea page

That page looks like this:

const RenameActivityArea = ({
  values,
  handleSubmit,
  handleChange,
  isValid,
  hideModal,
  isSubmitting,
  setFieldValue,
  ...props
}) => {
  const input = (name, inputProps) => (
    <Input
      {...getFormInputProps(
        {
          handleSubmit,
          handleChange,
          values,
          isValid,
          ...props,
        },
        name,
        inputProps
      )}
    />
  );
  return (
    <Form onSubmit={handleSubmit}>
      <Header.H2>{t("settings.activityAreas.modActivityAreas")}</Header.H2>
      {input("name", { label: "activityAreas.ActivityAreaName" })}
      <ButtonRow
        flex="0 0 auto"
        flow="row"
        justify="flex-end"
        padding="10px 0px"
      >
        <Button type="button" outline onClick={hideModal}>
          Fermer
        </Button>
        <Button
          loading={isSubmitting}
          disabled={!isValid}
          onClick={handleSubmit}
        >
          {t("entities.activityAreas.save")}
        </Button>
      </ButtonRow>
    </Form>
  );
};

const initialValues = {
  name: "",
};

const mapState = ({ entities }) => ({
  activity_area: entities.activity_area,
});

const mapDispatch = (dispatch) => ({
  onSubmit: (activity_area) => dispatch(updateActivityAreas(activity_area)),
});

RenameActivityArea.propTypes = {
  modalProps: PropTypes.shape(),
  handleSubmit: PropTypes.func,
  handleChange: PropTypes.func,
  hideModal: PropTypes.func,
  isSubmitting: PropTypes.bool,
  handleBlur: PropTypes.func,
  errors: PropTypes.shape(),
  touched: PropTypes.shape(),
  isValid: PropTypes.bool,
};

const FormWrapped = withForm({
  mapPropsToValues: () => initialValues,
  validateOnChange: false,
  validateOnBlur: true,
  validationSchema: schema,
  afterSubmit: (values, formik) =>
    formik.props.afterModalSubmit(values, formik),
})(RenameActivityArea);

const Connected = connect(mapState, mapDispatch)(FormWrapped);

export default Connected;

I can get the value inside the box if I do this:

<Input {...getFormInputProps({
    handleSubmit,
    handleChange,
    values: props.row, // <- This gives the value
    isValid,
    ...props,
    }, name, inputProps)}
/>

But then for some reason, I can’t seem to be allowed to modify the value inside the input. It’s like if it was read only or something. I am not sure thats the right way of approaching this anyway.

EDIT

getFormInputProps

export const getFormInputProps = (formProps, name, { label = name.replace('Attributes', ''), ...props } = {}) => {
  const error = browseObject(formProps.errors, name)
  const isTouched = browseObject(formProps.touched, name)
  return {
    onChange: formProps.handleChange,
    label: label && t(`entities.${label}`),
    error,
    name,
    value: browseObject(formProps.values, name) || '',
    onBlur: formProps.handleBlur,
    touched: isTouched,
    ...props,
  }
}

withForm

export default withForm({
  mapPropsToValues,
  validateOnChange: false,
  validateOnBlur: true,
  validationSchema: schema,
  afterSubmit: (_, { props: { history } }, { payload: { user } }) => {
    history.push(getRedirect(user))
  },
})

enter image description here

By adding the value inside the input field i can see it inside the modal but I can’t type or change it. The Input does not have any read-only restrictions but still does not allow typing. So I might not be doing this the right way.

It looks like it uses Formik
https://formik.org/docs/overview

EDIT:
The utils/form is the following. As you can see it uses Formik

import { withFormik } from 'formik'
import { debounce } from 'lodash'
import objectToFormData from 'object-to-formdata'
import * as yup from 'yup'
import API from '../config/api'
import t from './translate'
import { DEFAULT_TEXT_WRAP, DEFAULT_TEXT_WRAP_THRESHOLD } from '../config/constants'

yup.setLocale({
  mixed: {
    required: () => t('errors.fieldIsRequired'),
  },
})

const DEFAULT = (afterSubmit = () => { }) => ({
  handleSubmit: (values, formik) => {
    formik.props.onSubmit(values).then((action) => {
      formik.setSubmitting(false)
      afterSubmit(values, formik, action)
    })
  },
})

export default ({
  afterSubmit = (values, { props: { afterSubmit: after } }) => after && after(values),
  ...attrs
}) => withFormik(Object.assign({}, DEFAULT(afterSubmit), attrs))

export async function checkExists(url, params) {
  const response = await fetch(API.getUrl(url, params), {
    method: 'GET',
    headers: API.headers(),
  })
  return response
}

export const asyncSelectProps = formProps => key => ({
  onChange: (items) => {
    formProps.setFieldValue(key, items.map(item => item.id), false)
    if (!formProps.touched[key]) {
      formProps.setFieldTouched(key, true, false)
    }
    formProps.setFieldError(key, (!items.length) ? t('errors.mustContainAtLeastOneItem') : undefined, false)
  },
  touched: formProps.touched[key],
  error: formProps.errors[key],
})

/* eslint prefer-arrow-callback: 0 */
/* eslint func-names: 0 */
yup.addMethod(yup.string, 'asyncUnique', function (url, body, message) {
  return this.test({
    name: 'asyncUnique',
    message,
    test: debounce(async (value) => {
      if (value && value.length > 0) {
        const response = await checkExists(`/exists/${url}`, body(value))
        const { exists } = await response.json()
        return !exists
      }
      return true
    }, 500),
  })
})


export const browseObject = (object, path) => {
  const parsePath = (path.constructor === String) ? path.split('.') : path
  const [key, ...rest] = parsePath
  if (object && object[key] !== undefined) {
    const next = object[key]
    return (rest.length > 0) ? browseObject(next, rest) : next
  }
  return null
}

export const getFormInputProps = (formProps, name, { label = name.replace('Attributes', ''), ...props } = {}) => {
  const error = browseObject(formProps.errors, name)
  const isTouched = browseObject(formProps.touched, name)
  return {
    onChange: formProps.handleChange,
    label: label && t(`entities.${label}`),
    error,
    name,
    value: browseObject(formProps.values, name) || '',
    onBlur: formProps.handleBlur,
    touched: isTouched,
    ...props,
  }
}

export const preventPropagate = callback => (event) => {
  event.stopPropagation()
  callback(event)
}

export const extractFiles = (files, multiple = false) => (multiple ? files : files[0])

export const fileInputHandler = (formProps, name, multiple = false) => (event) => {
  const file = extractFiles(event.target.files, multiple)
  formProps.setFieldValue(name, file)
}

export const wrapText = (
  text,
  maximum = DEFAULT_TEXT_WRAP,
  threshold = DEFAULT_TEXT_WRAP_THRESHOLD,
) => {
  if ((text.length + threshold) > maximum) {
    return `${text.slice(0, maximum)}...`
  }
  return text
}
export const toFormData = body => objectToFormData(body)

export const PHONE_NUMBER_REGEX = /^(?([0-9]{3}))?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/
export const ZIP_CODE_REGEX = /^[A-Za-z]d[A-Za-z][ -]?d[A-Za-z]d$/


Get this bounty!!!

#StackBounty: #javascript #reactjs #react-native #graphql #formik Button Disables After Running Query Once

Bounty: 50

I have a screen where the user inputs a phone number. I run a graphql query loadUsers according to the input and then display the search results via the showUsersfunction. It works fine on the first time. I get the results. However, after that, when the results are conditionally rendered, the search button becomes disabled. So if I want to type in a different phone number and hit the search button again, I can’t do this. Unless I exit the screen and then come back. How can I fix this?

Here’s what my code looks like:

export const AddContactTry: React.FunctionComponent = () => {
  const initialValues: FormValues = {
    phoneNumber: '',
  };

  const [isSubmitted, setIsSubmitted] = useState(false);
  const [userData, setUserData] = useState<UsersLazyQueryHookResult>('');
  const navigation = useNavigation();
  const validationSchema = phoneNumberValidationSchema;

  const [
    createUserRelationMutation,
    {
      data: addingContactData,
      loading: addingContactLoading,
      error: addingContactError,
      called: isMutationCalled,
    },
  ] = useCreateUserRelationMutation({
    onCompleted: () => {
      Alert.alert('Contact Added');
    },
  });

  const showUsers = React.useCallback(
    (data: UsersLazyQueryHookResult) => {
      if (data) {
        return (
          <View style={styles.users}>
            {data.users.nodes.map(
              (item: { firstName: string; lastName: string; id: number }) => {
                const userName = item.firstName
                  .concat(' ')
                  .concat(item.lastName);
                return (
                  <View style={styles.item} key={item.id}>
                    <Thumbnail
                      style={styles.thumbnail}
                      source={{
                        uri:
                          'https://cdn4.iconfinder.com/data/icons/avatars-xmas-giveaway/128/afro_woman_female_person-512.png',
                      }}></Thumbnail>
                    <Text style={styles.userName}>{userName}</Text>
                    <View style={styles.addButtonContainer}>
                      <Button
                        rounded
                        style={styles.addButton}
                        onPress={() => {
                          addContact(Number(item.id));
                          setIsSubmitted(false);
                          setUserData(null);
                        }}>
                        <Icon
                          name="plus"
                          size={moderateScale(20)}
                          color="black"
                        />
                      </Button>
                    </View>
                  </View>
                );
              },
            )}
          </View>
        );
      }
    },
    [createUserRelationMutation, userData],
  );

  const addContact = React.useCallback(
    (id: Number) => {
      console.log('Whats the Id', id);
      createUserRelationMutation({
        variables: {
          input: { relatedUserId: id, type: RelationType.Contact, userId: 30 },
        },
      });
    },
    [createUserRelationMutation],
  );

  const getContactId = React.useCallback(
    (data: UsersLazyQueryHookResult) => {
      if (data) {
        if (data.users.nodes.length == 0) {
          Alert.alert('No User Found');
        } else {
          setUserData(data);
        }
      }
    },
    [addContact],
  );

  const [loadUsers] = useUsersLazyQuery({
    onCompleted: getContactId,
    onError: _onLoadUserError,
  });

  const handleSubmitForm = React.useCallback(
    (values: FormValues, helpers: FormikHelpers<FormValues>) => {
      setIsSubmitted(true);
      const plusSign = '+';
      const newPhoneNumber = plusSign.concat(values.phoneNumber);
      loadUsers({
        variables: {
          where: { phoneNumber: newPhoneNumber },
        },
      });
      values.phoneNumber = '';
    },
    [loadUsers],
  );

  return (
    <SafeAreaView>
      <View style={styles.container}>
        <View style={styles.searchTopContainer}>
          <View style={styles.searchTopTextContainer}>
          </View>
          <View>
            <Formik
              initialValues={initialValues}
              onSubmit={handleSubmitForm}
              validationSchema={validationSchema}
              >
              {({ handleChange, handleBlur, handleSubmit, values, isValid, dirty }) => (
                <View style={styles.searchFieldContainer}>
                  <View style={styles.form}>
                    <FieldInput style={styles.fieldInput}
                      handleChange={handleChange}
                      handleBlur={handleBlur}
                      value={values.phoneNumber}
                      fieldType="phoneNumber"
                      icon="phone"
                      placeholderText="49152901820"
                    />
                    <ErrorMessage
                      name="phoneNumber"
                      render={(msg) => (
                        <Text style={styles.errorText}>{msg}</Text>
                      )}
                    />
                  </View>
                  <View style={styles.buttonContainer}>
                  <Text>Abbrechen</Text>
                </Button>
                <Button
                  block
                  success
                  disabled={!isValid || !dirty}
                  onPress={handleSubmit}
                  style={styles.button}>
                  <Text>Speichern</Text>
                </Button>
                  </View>
                </View>
              )}
            </Formik>
          </View>
          {isSubmitted && showUsers(userData)}
        </View>
      </View>
    </SafeAreaView>
  );
};

Edit:

As suggested in comments, I tried using useFormik instead of but couldn’t make that work.

export const AddContactTry: React.FunctionComponent = () => {
  const { values, handleChange, handleSubmit, dirty, handleBlur, isValid}= useFormik({
    initialValues: {
      phoneNumber: '',
    },
    onSubmit: values => {
      handleSubmitForm(values);
    },
  });

  const [isSubmitted, setIsSubmitted] = useState(false);
  const [userData, setUserData] = useState<UsersLazyQueryHookResult>('');
  const navigation = useNavigation();
  const validationSchema = phoneNumberValidationSchema;

  const [
    createUserRelationMutation,
    {
      data: addingContactData,
      loading: addingContactLoading,
      error: addingContactError,
      called: isMutationCalled,
    },
  ] = useCreateUserRelationMutation({
    onCompleted: () => {
      Alert.alert('Contact Added');
    },
  });

  const showUsers = React.useCallback(
    (data: UsersLazyQueryHookResult) => {
      if (data) {
        return (
          <View style={styles.users}>
            {data.users.nodes.map(
              (item: { firstName: string; lastName: string; id: number }) => {
                const userName = item.firstName
                  .concat(' ')
                  .concat(item.lastName);
                return (
                  <View style={styles.item} key={item.id}>
                    <Thumbnail
                      style={styles.thumbnail}
                      source={{
                        uri:
                          'https://cdn4.iconfinder.com/data/icons/avatars-xmas-giveaway/128/afro_woman_female_person-512.png',
                      }}></Thumbnail>
                    <Text style={styles.userName}>{userName}</Text>
                    <View style={styles.addButtonContainer}>
                      <Button
                        rounded
                        style={styles.addButton}
                        onPress={() => {
                          addContact(Number(item.id));
                          setIsSubmitted(false);
                          setUserData(null);
                        }}>
                        <Icon
                          name="plus"
                          size={moderateScale(20)}
                          color="black"
                        />
                      </Button>
                    </View>
                  </View>
                );
              },
            )}
          </View>
        );
      }
    },
    [createUserRelationMutation, userData],
  );

  const addContact = React.useCallback(
    (id: Number) => {
      console.log('Whats the Id', id);
      createUserRelationMutation({
        variables: {
          input: { relatedUserId: id, type: RelationType.Contact, userId: 30 },
        },
      });
    },
    [createUserRelationMutation],
  );

  const getContactId = React.useCallback(
    (data: UsersLazyQueryHookResult) => {
      if (data) {
        if (data.users.nodes.length == 0) {
          Alert.alert('No User Found');
        } else {
          setUserData(data);
        }
      }
    },
    [addContact],
  );

  const [loadUsers] = useUsersLazyQuery({
    onCompleted: getContactId,
    onError: _onLoadUserError,
  });

  const handleSubmitForm = React.useCallback(
    (values: FormValues) => {
      setIsSubmitted(true);
      const plusSign = '+';
      const newPhoneNumber = plusSign.concat(values.phoneNumber);
      console.log('Submitted');
      loadUsers({
        variables: {
          where: { phoneNumber: newPhoneNumber },
        },
      });
      values.phoneNumber = '';
    },
    [loadUsers],
  );

return (
    <SafeAreaView>
      <View style={styles.container}>
        <View style={styles.searchTopContainer}>
          <View style={styles.searchTopTextContainer}>
          </View>
          <View>
                <View style={styles.searchFieldContainer}>
                  <View style={styles.form}>
                    {/* <FieldInput style={styles.fieldInput}
                      handleChange={handleChange}
                      handleBlur={Formik.handleBlur}
                      value={Formik.values.phoneNumber}
                      fieldType="phoneNumber"
                      icon="phone"
                      placeholderText="49152901820"
                    /> */}
                     <Input style={styles.fieldInput}
                      onChangeText={handleChange('phoneNumber') as (text: string) => void}
                      onBlur={handleBlur('phoneNumber') as (event: any) => void}
                      value={values.phoneNumber}
                      //name="phoneNumber"
                      //fieldType="phoneNumber"
                      //icon="phone"
                      placeholder="49152901820"
                    />
                    <ErrorMessage
                      name="phoneNumber"
                      render={(msg) => (
                        <Text style={styles.errorText}>{msg}</Text>
                      )}
                    />
                  </View>
                  <View style={styles.buttonContainer}>
                <Button
                  block
                  success
                  disabled={!isValid || !dirty}
                  onPress={handleSubmit}
                  style={styles.button}>
                  <Text>Speichern</Text>
                </Button>
                  </View>
                </View>
          </View>
          {isSubmitted && showUsers(userData)}
        </View>
      </View>
    </SafeAreaView>
  );
};


Get this bounty!!!

#StackBounty: #material-ui #formik #yup #formik-material-ui Duplicate Errors on Formik/Yup Validation

Bounty: 50

I’m using the following validation schema in Formik:

validationSchema = {
  Yup.object({
    emails: Yup.array()
      .of(Yup.string().email('Please enter valid email addresses only.'))
      .min(1, 'At least one email address is required.')
  })
}

It works very well, except that, since I’m using a Material UI AutoComplete component, when the user enters multiple invalid email addresses, they get to see the error message once per invalid email address.

Any way around this?


Get this bounty!!!