ionic-team / ionic-framework

A powerful cross-platform UI toolkit for building native-quality iOS, Android, and Progressive Web Apps with HTML, CSS, and JavaScript.
https://ionicframework.com
MIT License
51.09k stars 13.51k forks source link

bug: React Hoof Forms/Ionic React: Fulfilling input value automatically based on input value tilting #22718

Open SalahAdDin opened 3 years ago

SalahAdDin commented 3 years ago

Bug Report

Ionic version:

[ ] 4.x [x] 5.5.2

Current behavior:

Screen Recording 2020-12-24 at 18 50 41

It is imposible to handle both values when the current's input value is changing.

Expected behavior:

The current input must have a normal behavior while the other input's value changes as the current input's value change.

Steps to reproduce:

Just go to the input and change its value.

Related code:

With the following code it was working fine:

<form onSubmit={handleSubmit(onSubmit)}>
                  <IonGrid>
                    <IonRow>
                      <IonCol size="7">
                        <IonItem>
                          <IonLabel position="floating" color="primary">
                            Cash Amount
                          </IonLabel>
                          <IonInput
                            name="cashValue"
                            type="number"
                            ref={register(validationRules) as IonicRef}
                            onIonChange={(e) =>
                              setValue(
                                "posValue",
                                calculateFieldValue(e.detail.value)
                              )
                            }
                            required
                          />
                          {errors?.cashValue && (
                            <IonAlert
                              isOpen={true}
                              header="Error: Cash Amount"
                              message={errors.cashValue?.message}
                              buttons={["OK"]}
                            />
                          )}
                        </IonItem>
                        <IonItem>
                          <IonLabel position="floating" color="primary">
                            POS Amount
                          </IonLabel>
                          <IonInput
                            name="posValue"
                            type="number"
                            ref={register(validationRules) as IonicRef}
                            onIonChange={(e) =>
                              setValue(
                                "cashValue",
                                calculateFieldValue(e.detail.value)
                              )
                            }
                            required
                          />
                          {errors?.posValue && (
                            <IonAlert
                              isOpen={true}
                              header="Error: POS Amount"
                              message={errors.posValue?.message}
                              buttons={["OK"]}
                            />
                          )}
                        </IonItem>
                      </IonCol>
                      <IonCol size="5" className="package-price">
                        <IonItem lines="none">
                          {packageDetails?.orderData?.currencySymbol}
                          {packagePrice}
                        </IonItem>
                        <IonButton
                          color="tertiary"
                          type="submit"
                          disabled={!formState.isDirty}
                        >
                          Pay
                        </IonButton>
                      </IonCol>
                    </IonRow>
                  </IonGrid>
                </form>

It was working also with this other code:

const [formChangeFieldName, setFormChangeFieldName] = useState<'cashAmount' | 'posAmount'>()
  const formMethods = useForm<PackagePaymentDto>();
  const { register, watch, errors, handleSubmit } = formMethods;
  const { cashAmount, posAmount } = watch()
---
const onChangeInput = (e: any & { target: { name: string } }) => setFormChangeFieldName(e.target.name)
---
<IonInput
                    name="cashAmount"
                    type="number"
                    value={
                      !formChangeFieldName || formChangeFieldName === "cashAmount"
                        ? cashAmount
                        : packagePrice - posAmount
                    }
                    onIonChange={onChangeInput}
                    defaultValue={0}
                    ref={
                      register({
                        required: true,
                        min: 0,
                        max: packageDetails.orderData.price,
                      }) as any
                    }
                  />
---
<IonInput
                    name="posAmount"
                    type="number"
                    value={
                      !formChangeFieldName || formChangeFieldName === "posAmount"
                        ? posAmount
                        : packagePrice - cashAmount
                    }
                    onIonChange={onChangeInput}
                    ref={
                      register({
                        required: true,
                        min: 0,
                        max: packageDetails.orderData.price,
                      }) as any
                    }
                  />

This was working pretty fine, but after update Ionic to 5.5.2 it stopped to work: onIonChange is not triggered(ensured by log), avoiding to launch setFormChangeFieldName(e.target.name).

So, We developed the next code:

<form noValidate onSubmit={handleSubmit(onSubmitPayment)}>
              <IonItem>
                <IonLabel>Nakit</IonLabel>
                <Controller
                  control={control}
                  name="cashAmount"
                  rules={{
                    required: true,
                    min: 0,
                    max: packageDetails.orderData.price,
                  }}
                  render={({ value, onChange, onBlur }) => (
                    <IonInput
                      type="number"
                      value={value}
                      onIonChange={([e]:any) => {
                        console.log("Firing onChange at Cash!")
                        setValue("posAmount", calculateFieldValue(e.detail.value!))
                        // onChange(e.detail.value!)
                      }}
                      onIonBlur={onBlur}
                      required
                    />
                  )}
                />
              </IonItem>
              {formErrorMessage(errors.cashAmount)}

              <IonItem>
                <IonLabel>Pos</IonLabel>
                <Controller
                  control={control}
                  name="posAmount"
                  rules={{
                    required: true,
                    min: 0,
                    max: packageDetails.orderData.price,
                  }}
                  render={({ value, onChange, onBlur }) => (
                    <IonInput
                      type="number"
                      value={value}
                      onIonChange={(e: any) => {
                        console.log("Firing onChange at POS!")
                        setValue("cashAmount", calculateFieldValue(e.detail.value!))
                        // onChange(e.detail.value!)
                      }}
                      onIonBlur={onBlur}
                      required
                    />
                  )}
                />
              </IonItem>
              {formErrorMessage(errors.posAmount)}

              <IonButton
                disabled={
                  isPaidPrice ||
                  packageDetails?.orderData?.status === PACKAGE_STATUS.DELIVERED
                }
                type="submit"
                color="secondary"
                className="ion-float-right"
              >
                Ödeme yap
              </IonButton>
              <IonItem lines="none" className="package-price">
                {packageDetails.orderData.currencySymbol}
                {packageDetails.orderData.price}
              </IonItem>
            </form>

Other information:

We opened a question on StackOverflow in order to get an answer about this concern.

Ionic info:

❯ ionic info

Ionic:

   Ionic CLI       : 6.12.3 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework : @ionic/react 5.5.2

Capacitor:

   Capacitor CLI   : 2.4.5
   @capacitor/core : 2.4.5

Utility:

   cordova-res : not installed
   native-run  : not installed

System:

   NodeJS : v15.4.0 (/usr/local/Cellar/node/15.4.0/bin/node)
   npm    : 7.0.15
   OS     : macOS Big Sur
ionitron-bot[bot] commented 3 years ago

Thanks for the issue! This issue has been labeled as holiday triage. With the winter holidays quickly approaching, much of the Ionic Team will soon be taking time off. During this time, issue triaging and PR review will be delayed until the team begins to return. After this period, we will work to ensure that all new issues are properly triaged and that new PRs are reviewed.

In the meantime, please read our Winter Holiday Triage Guide for information on how to ensure that your issue is triaged correctly.

Thank you!

felquis commented 3 years ago

I want to do a follow up on this issues, just because it is 7 months old and I also ran into related problem with react-hook-form.

When I type-in and I use the physical keyboard to submit the form, react-hook-form register does not change the input value, so at onSubmit its value is still undefined. It will only update when I lose focus on the input.

Could be a problem with react-hook-form, but using the web native input it works, also with chakra-ui Input it also works.

How to reproduce:

import { IonButton, IonInput } from "@ionic/react";
import { useCallback, useState } from "react";
import { useForm } from "react-hook-form";

const SimpleForm = () => {
  const { handleSubmit, register } = useForm();
  const [text, setText] = useState("");

  const onSubmit = useCallback((data) => {
    console.log("data", data);
    setText(data.fieldName);
  }, []);

  return (
    <>
      <form onSubmit={handleSubmit(onSubmit)}>
        <IonInput {...register("fieldName")} placeholder="ionInput react" />

        <IonButton type="submit">Submit</IonButton>

        {text === undefined ? <p>fieldName is undefined</p> : null}
        {text === "" ? <p>fieldName is empty string</p> : null}
        {text ? (
          <p>
            fieldName is {'"'}
            {text}
            {'"'} string
          </p>
        ) : null}

        <ul>
          <li>Type anything, do not loose focus and:</li>
          <li>
            <ul>
              <li>Try to submit with the keyboard (physical or virtual)</li>
              <li>Try to submit with direct hit at submit button</li>
            </ul>
          </li>
        </ul>
      </form>
    </>
  );
};

export default SimpleForm;

It is all about the onChange happening onBlur and not onChange at all, which is different from web native input.

A collaborator could confirm if it's an issue, I can also create a new issue if it's the case.