s-yadav / react-number-format

React component to format numbers in an input or as a text.
MIT License
3.88k stars 409 forks source link

Unformatted value (or floatValue) within onChange #264

Open benneq opened 5 years ago

benneq commented 5 years ago

Is there a way to get the whole ChangeEvent with the unformatted value (or float value)?

I'm using formik and therefore need to use onChange, because it needs the ChangeEvent data to determine which field has changed.

The problem is, that onChange's event.target.value contains the formatted value. But I need the unformatted value (or float value).

<NumberFormat
  onChange={(event) => {
    console.log(event); // I need this event
    console.log(event.target.value); // This is the formatted value, I don't want
  }}
  onValueChange={(values) => {
    console.log(values.value); // Here's the unformatted value, I want
    console.log(values.floatValue); // Here's the float value, which is okay, too
  }}
  prefix="$"
/>

In other words: I need onChange with the value provided by onValueChange.

Is there any way to get this working?

IraYU commented 5 years ago

I think you can use state. To pass with onValueChange values.value to some variable in your state. And then use is with your onChange function.

benneq commented 5 years ago

You mean that I should listen to both onValueChange and onChange? Is is guaranteed, that onValueChange is getting called first?

And then, you mean something like this?

onValueChange={values =>
  this.setState({value: values.floatValue})
}
onChange={e => {
  e.persist();
  e.target = {
    ...e.target,
    value: this.state.value
  }
})
s-yadav commented 5 years ago

Yes onValueChange will always be called before onChange. But value can change on blur event as well which will trigger onValueChange but not onChange. Even onValueChange will be called when there is a prop change, in that case there is no event involved. That's why onValueChange does not receive any event object.

Formik has setFieldValue method which you should use for your case. See example of using Formik with 3rd Party Input Component https://codesandbox.io/s/73jj9zom96

Here example is with the react-select. But same thing can be done for react-number-format.

leifoolsen commented 5 years ago

I have a similar problem: I dynamically generate the input fields with React.CreateElement. The idea is that the generated input fields should have a common onValueChange eventHandler. Since onValueChange does not return the event, it is not possible to identify the component that is the source of the onValueChange. This was possible earlier when the event was returned as part of onValueChange. One possibility is to use onChange, but then one needs an "unformat" utility function. Another option is to reintroduce the event as part of onValueChange.

benneq commented 5 years ago

That's a general "problem" of the React ecosystem. For my apps I generally build wrapper components for nearly everything. This means: I have an own component library, which internally uses 3rd party components like material, number-format, formik, etc... And in the app's code I use my own component library.

For my purpose with Formik I created a pair of components, which handle this for me. It doesn't use onChange of NumberFormat.

NumberField.tsx (which is a simple wrapper for React Material):

type NumberFieldProps = Omit<TextFieldProps, 'type' | 'value' | 'InputProps'> & {
    value: number
    onValueChange?: (values: NumberFormatValues) => void
    allowNegative?: boolean
    decimalScale?: number
    InputProps?: Omit<TextFieldProps["InputProps"], 'inputComponent'>
};

const NumberFormatCustom = (props: any) => {
    const { inputRef, onValueChange, ...other } = props;

    return (
        <NumberFormat
            {...other}
            getInputRef={inputRef}
            onValueChange={onValueChange}
            thousandSeparator={'.'}
            decimalSeparator={','}
        />
    );
}

const NumberField: React.FunctionComponent<NumberFieldProps> = (props) => {
    const { inputProps, InputProps, onValueChange, allowNegative, decimalScale, ...rest } = props;

    return (
        <TextField
            inputProps={{
                ...inputProps,
                onValueChange: onValueChange,
                allowNegative: allowNegative,
                decimalScale: decimalScale
            }}
            InputProps={{
                ...InputProps,
                inputComponent: NumberFormatCustom
            }}
            {...rest}
        />
    );
}

And then I have my FormNumberField.tsx, which is a wrapper using Formik + my NumberField component:

type Props = Omit<NumberFieldProps, 'error' | 'onChange' | 'onBlur' | 'value' | 'component'> & {
    component?: React.ComponentType<NumberFieldProps>
}

const FormNumberField: React.FunctionComponent<Props> = (props) => {
    const { name, component: Component = NumberField, helperText, ...rest } = props;

    return (
        <Field
            name={name}
            render={({field, form: { touched, errors, setFieldValue }}: FieldProps) => {
                const fieldError = getIn(errors, name);
                const showError = getIn(touched, name) && !!fieldError;

                return (
                    <Component
                        error={showError}
                        helperText={showError ? fieldError : helperText}
                        onValueChange={values => setFieldValue(name, values.floatValue)}
                        {...field}
                        onChange={undefined}
                        {...rest}
                    />
                )
            }
        }/>
    );
}
leifoolsen commented 5 years ago

I ended up with a wrapper as well:

import React from 'react';
import PropTypes from 'prop-types';
import NumberFormat from 'react-number-format';
import {TextField} from './text-field';

const noop = () => {};

export const NumberField = ({
  id,
  name,
  onValueChange,
  ...otherProps
}) => {
  const handleValueChange = (valueObject) => onValueChange(id, name, valueObject);

  return (
    <NumberFormat id={id} name={name} {...otherProps} customInput={TextField} onValueChange={handleValueChange} />
  );
};

NumberField.propTypes = {
  /**
   * Specify an `id` for the <input>
   */
  id: PropTypes.string.isRequired,

  /**
   * Provide a name for the underlying <input> node
   */
  name: PropTypes.string,

  /**
   * Provide an optional `onValueChange` hook that is called each time the
   * the underlying <input> value changes. Accepts `id`, `name` and `valueObject`
   */
  onValueChange: PropTypes.func,
};

NumberField.defaultProps = {
  onValueChange: noop,
};
Ashish1857 commented 5 years ago

I have one concern, I need to work on onchange. While typing some value i need to generate some other value. How can i achieve this.

image

on basis of min/max the range will be calculated, so it was working fine if i dont change raidio button, but when am changing radion button again onvaluechange was getting called.

how can i work using onChange function

s-yadav commented 5 years ago

onValueChange gets called when the prop changes and cause format value to change. Most of the time that is the usecase, I am not sure whats the expected result you are looking for here, If you can tell whats the behavior and whats the expected behaviour, I can help you better.

rsilvamanayalle commented 5 years ago

Is there a way to get the whole ChangeEvent with the unformatted value (or float value)?

I'm using formik and therefore need to use onChange, because it needs the ChangeEvent data to determine which field has changed.

The problem is, that onChange's event.target.value contains the formatted value. But I need the unformatted value (or float value).

<NumberFormat
  onChange={(event) => {
    console.log(event); // I need this event
    console.log(event.target.value); // This is the formatted value, I don't want
  }}
  onValueChange={(values) => {
    console.log(values.value); // Here's the unformatted value, I want
    console.log(values.floatValue); // Here's the float value, which is okay, too
  }}
  prefix="$"
/>

In other words: I need onChange with the value provided by onValueChange.

Is there any way to get this working?

I don't know if this a pretty solution, but it work well for me...

const PrettyInput = ({ onChange, ...rest }) => {
  let floatValue = 0;
  return (
    <NumberFormat
      onValueChange={values => (floatValue = values.floatValue)}
      onChange={() => onChange(floatValue || '')}
      {...rest}
    />
  );
};
eMarek commented 5 years ago

I think that majority of us have problems when upgrading to v4 when this was changed:

Trigger onValueChange if the value is formatted due to prop change.

While onChange is triggered only at typing, onValueChange is triggered at typing and at prop change event. This is crucial! What most of us need is pure value at onChange listener and this is not possible anymore out of the box. I've tested it @rsilvamanayalle solution and it does work, but we are relying on that onChangeValue is called before onChange which is bad.

rpk-red commented 4 years ago

Is this fixed? Because I can not get floatValue in onChange at the moment in version: 4.3.1. And I can not find change log anywhere.

osvaldokalvaitir commented 4 years ago

I'm using formik and I need it too

R4DIC4L commented 3 years ago

I see that onValueChanged is called on blur with an object having the keys, but undefined in all of them ({formattedValue: "", value: "", floatValue: undefined}), when number has more than 3 digits and thousandSeparator="," is used. I saved the value from onValueChanged for using later in onChange, it seems onChange is no longer called right after this for blur. I checked this in conjunction with material-ui text field as below.

Any way of by-passing this blur function? It is making my text field unusable with redux-form, as the field is cleared after blur.

import TextField from '@material-ui/core/TextField';

const hasValue = (value) =>
  value !== undefined && value !== null;

function MUIReduxFormTextField(props) {
  const {
    InputProps,
    onChange,
    ...other
  } = props;
  const fieldInputProps = other.type === 'number'
    ? { ...InputProps, inputComponent: NumericFormat }
    : InputProps;

  return (
    <TextField
      InputProps={fieldInputProps}
      onChange={onChange}
      {...other}
      type={other.type === 'number' ? 'text' : other.type}
    />
  );
}

class NumericFormat extends Component {
  render() {
    const { inputRef, onChange, min, max, ...other } = this.props;

    const numericValue = other.value ? Number(other.value) : 0;

    return (
      <NumberFormat
        {...other}
        value={numericValue}
        getInputRef={inputRef}
        onChange={(event) => {
          if (this.changed) {
            if (onChange) {
              onChange(this.changedValue);
            }
            this.changed = false;
            this.changedValue = undefined;
          }
        }}
        onValueChange={(values) => {
          this.changed = true;
          this.changedValue = values.value;
        }}
        thousandSeparator=","
        decimalSeparator="."
        isAllowed={(values) => {
          const { floatValue } = values;
          return (!hasValue(min) || floatValue >= min) && (!hasValue(max) || floatValue <= max);
        }}
      />
    );
  }
}
s-yadav commented 3 years ago

@R4DIC4L Can you create a sandbox with the example above. It will be easier to debug that way.

R4DIC4L commented 3 years ago

@s-yadav Here is a code sandbox: https://codesandbox.io/s/material-ui-number-format-e1tph?file=/demo.js.

I found the problem, the value returned in the redux-form submit function is sending the formatted text instead of number. I cannot explain this as I am sending the floatNumber in the onChange handler (as can be seen in NumericFormat component from numeric-format.js file). Any ideas why this is happening and if there is any way of making it send the number instead? If I kept the type="number" on NumberFormat, the HTML validation would kick in and not allow comma separator for the thousands.

Edit: I logged the value in the NumericFormat component and it is a number, not a string. It seems that it is changed to a string on blur event.

R4DIC4L commented 3 years ago

@s-yadav Please let me know if you want me to add a separate GitHub issue for this, I am not sure it is related to the unformatted value item. Although, it might be related to the onValueChange being triggered on blur without an onChange event with the undefined values.

nikhil-varma commented 3 years ago

@R4DIC4L Thanks for the CodeSandbox link. I am investigating this one but in the meantime, as you suggested.. can you please create another issue with the steps to reproduce it..

Based on some preliminary investigation.. I am unable to see onValueChange being triggered on blur although I am still trying to debug this situation. Let's pick this up in a new issue in order to keep concerns separated. Thanks for your understanding!

R4DIC4L commented 3 years ago

@nikhil-varma @s-yadav I have added item https://github.com/s-yadav/react-number-format/issues/554 for the above scenario.

nikhil-varma commented 3 years ago

@R4DIC4L Thanks!

eliobricenov commented 3 years ago

I think that majority of us have problems when upgrading to v4 when this was changed:

Trigger onValueChange if the value is formatted due to prop change.

While onChange is triggered only at typing, onValueChange is triggered at typing and at prop change event. This is crucial! What most of us need is pure value at onChange listener and this is not possible anymore out of the box. I've tested it @rsilvamanayalle solution and it does work, but we are relying on that onChangeValue is called before onChange which is bad.

I agree on this, I would suggest having the same props that we have available in the onValueChanges handle in the onChange event. This becomes troublesome with controlled inputs because the value of the input in the state might not always change due to a user interaction (useEffect for example).

@nikhil-varma

pavankjadda commented 3 years ago

Yes onValueChange will always be called before onChange. But value can change on blur event as well which will trigger onValueChange but not onChange. Even onValueChange will be called when there is a prop change, in that case there is no event involved. That's why onValueChange does not receive any event object.

Formik has setFieldValue method which you should use for your case. See example of using Formik with 3rd Party Input Component https://codesandbox.io/s/73jj9zom96

Here example is with the react-select. But same thing can be done for react-number-format.

But onChange has formatted value not the original value. I don't think your example addresses the actual issue.

onChange={(event: any) => {console.log("version", event?.target?.value)}}
jersrej commented 3 years ago

I had quite the same problem with onChangeValue vs onChange and had to use onChange wherein the value is being stripped so the value won't return as the formatted one, others might take it as a weird solution but my scenario is when input1 is being changed or typed into, the input2 will also change but without typing and just setting a value depending on the formula and that's vice versa, the problem I had is that onChangeValue triggers everytime even If I didn't type into it

onChange={(e: any) => {
  const stripValue = e.target.value.replace(/[^\d.-]/g, '');
  console.log(stripValue)
}}
alang79 commented 3 years ago

onValueChange gets called when the prop changes and cause format value to change. Most of the time that is the usecase, I am not sure whats the expected result you are looking for here, If you can tell whats the behavior and whats the expected behaviour, I can help you better.

There is a slight difference between: a) User is making a change to a state property via the UI b) Application is intializing values from persisted data

In case of a) I expect the app to invoke the "change event" but in case of b) I do not expect the system to invoke the "change event". For a) the corresponding data has been truly changed - for b) the data has not changed, the application is rendering the data as it is existing within the persisted data.

In our case the function invoked by onValueChange is automatically updating the data in the redux store and is also setting some flag indicating that the data was modified which leads to a button "Save Modifcations" & "Cancel Modifications" to appear on top of some list view. Obviously we do not want to flag that data has been changed when setting the intial values of the component.

onChange works as expected but only delivers the formatted value.. now we need to implement some hack to differenciate the user case a) & b) and make sure that the redux store is not updated in case of b).

The workaround to use the combination of onValueChange & onChange works - but this is not straight forward to me.

Tronglx commented 3 years ago

Same issue, if using a format library and then cannot get original value, it will be useless. Have anyone suggest another solution or library?