Button Disables After Running Query Once

Viewed 390

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 and moved showUsers to a separate component but it didn't work either. The button still gets disabled after first query.

export const AddContactTry: React.FunctionComponent = () => {
  const validationSchema = phoneNumberValidationSchema;

  const { values, handleChange, handleSubmit, dirty, handleBlur, isValid, resetForm, isSubmitting, setSubmitting, touched}= useFormik({
    initialValues: {
      phoneNumber: '',
    },
    //isInitialValid:false,
    validationSchema,
    onSubmit: (values: FormValues) => {
      handleSubmitForm(values);
    },
  });

  console.log('isDirty', dirty);
  console.log('isValid', isValid);
  console.log('phone numm', values.phoneNumber);
  console.log('submitting status', isSubmitting);

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

  const _onLoadUserError = React.useCallback((error: ApolloError) => {
    Alert.alert('Oops, try again later');
  }, []);

  // 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) => {
      //resetForm();
      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 },
        },
      });
      resetForm();
    },
    [loadUsers],
  );

  // if (!addingContactLoading && isMutationCalled) {
  //   if (addingContactError) {
  //     Alert.alert('Unable to Add Contact');
  //   }
  // }

  return (
    <SafeAreaView>
      <View style={styles.container}>
        <View style={styles.searchTopContainer}>
          <View>
                <View style={styles.searchFieldContainer}>
                  <View style={styles.form}>
                    <Item underline style={styles.newFieldInput} >
                      <Icon name="mobile" color="black" size={26}></Icon>
                     <Input 
                      onChangeText={handleChange('phoneNumber') as (text: string) => void}
                      onBlur={handleBlur('phoneNumber') as (event: any) => void}
                      value={values.phoneNumber}
                      placeholder="49152901820"
                    />
                    </Item>
                  </View>
                  <View style={styles.buttonContainer}>
                       <Button
                  block
                  danger
                  bordered
                  style={styles.button}
                  // onPress={() => navigation.goBack()}
                  //disabled={!isValid || !dirty}
                  //disabled={isSubmitting}
                  onPress={resetForm}                  
                  >
                  <Text>Abbrechen</Text>
                </Button>
                <Button
                  block
                  success
                  disabled={!isValid || !dirty}
                  onPress={handleSubmit}
                  style={styles.button}>
                  <Text>Speichern</Text>
                </Button>
                  </View>
                </View>
          </View>
          {/* {isSubmitted && showUsers(userData)} */}
          <User data={userData}></User>
        </View>
      </View>
    </SafeAreaView>
  );
};
type UserProps = {
  data: UsersLazyQueryHookResult;
  //isSubmitted: boolean;
};
export const User: React.FunctionComponent<UserProps> = ({
  data,
  //isSubmitted,
}) => {
  console.log('user called');
  const [
    createUserRelationMutation,
    {
      data: addingContactData,
      loading: addingContactLoading,
      error: addingContactError,
      called: isMutationCalled,
    },
  ] = useCreateUserRelationMutation({
    onCompleted: () => {
      Alert.alert('Contact Added');
    },
  });

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

  if (!addingContactLoading && isMutationCalled) {
    if (addingContactError) {
      Alert.alert('Unable to Add Contact');
    }
  }
  if (!data) return null;
  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>
  );
};

The button is supposed be disabled (grey) when it's empty (not dirty) and not valid (```!isValid). If it's dirty and valid, the button turns to green. Currently, after running the first query and getting the results, If I type something valid into the input field, the button does turn to green from grey. However, I cannot 'click' on it.

2 Answers

Make few changes to your code and see if it works:

  • make <Button> type submit.
  • Make sure to provide a name (phoneNumber) to your input. This is how formik tracks the form values.
<FieldInput style={styles.fieldInput}
 handleChange={handleChange}
 handleBlur={handleBlur}
 value={values.phoneNumber}
 fieldType="phoneNumber"
 name="phoneNumber" //<<<<<<<--- like this
 icon="phone"
 placeholderText="49152901820"
/>
  • use <form> tag inside <Formik>. Have an onSubmit.

for example:

<Formik
      initialValues={{ name: 'jared' }}
      onSubmit={(values, actions) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2));
          actions.setSubmitting(false);
        }, 1000);
      }}
    >
    {({ handleChange, handleBlur, handleSubmit, values, isValid, dirty }) => (
        <form onSubmit={props.handleSubmit}>
          <input
            type="text"
            onChange={props.handleChange}
            onBlur={props.handleBlur}
            value={props.values.name}
            name="name"
          />
          {props.errors.name && <div id="feedback">{props.errors.name}</div>}
          <button type="submit">Submit</button>
        </form>
      )}
    </Formik>
const handleSubmitForm = React.useCallback(
    (values: FormValues, formikBag: any) => {
      setIsSubmitted(true);
      const plusSign = '+';
      const newPhoneNumber = plusSign.concat(values.phoneNumber);
      console.log('Submitted');
      loadUsers({
        variables: {
          where: { phoneNumber: newPhoneNumber },
        },
      });
    //   values.phoneNumber = ''; //<------don't do this.. probably this could be issue as well
    formikBag.resetForm()
    },
    [loadUsers],
  );

  • remove all React.useCallbacks. Once your form is working then add it one by one to required methods

Following comment's disscussion (enter link description here) it looks like React Native fails in some specific scenarios. Updated state/variables are not properly reflected to rendered view (button rerendered as not disabled doesn't work).

It's not Formik's fault ... using useFormik gives possibility to access values and helpers in entire component. resetForm called from handlers works properly.

My advise is to extract showUsers into separate [functional] component, f.e.

 {userData && <UserList data={userData} />}

or at least use key in rendered <View /> components at the levels where there is more than one (showUsers rendered <View/> at a sibbling to <View style={styles.searchTopContainer}> ). Using key helps react to manage virtual DOM and update view. Separate component in fact does the same but also reduces this component complexity.

Related