sanniassin / react-input-mask

Input masking component for React. Made with attention to UX.
MIT License
2.22k stars 258 forks source link

Support currency formating #192

Open iloveip opened 4 years ago

iloveip commented 4 years ago

Hi there,

Is there anyway to use this library for currency formating? For example, if I need to allow unknown length of digits before decimal point and only one or two digits after the decimal point?

There was a similar issue #44, but it has been closed.

patricklongo1 commented 4 years ago

up

annezao commented 4 years ago

up

DreadCube commented 4 years ago

up

meerbaum commented 4 years ago

up

VictorHAS commented 4 years ago

up

xbrunosousa commented 4 years ago

up

Hopp3r commented 4 years ago

I'm also interested in this.

bonzoSPb commented 3 years ago

@sanniassin can we get an answer?

augustosnk12 commented 3 years ago

Also need that

JohanAltamar commented 3 years ago

I have an alternative using react hooks. I'll share it with you...

// some code here
const [budget, setBudget] = useState(null) //any number
const [format, setFormat] = useState('9') //initial format

useEffect(() => {
  if (budget) {
    const num = budget.toString()
    const len = num.length
    const arr = []

    for (let i = 0; i < len; i++) {
      arr.push('9');
      if ((i + 1) % 3 == 0 && i !== len - 1) {
        arr.push('.'); //this can be change with any separator (. or ,)
      }
    }
    const newFormat = arr.reverse().join('');
    setFormat(`$ ${newFormat}`);
  }
}, [budget]);

return(
  //some code here
  <InputMask
    mask={format}
    maskChar={null}
    value={budget}
    onChange={handleInputChange}
    onBlur={handleBlur}
    inputProps={{
      step: 50000,
      min: minBudget,
      max: maxBudget,
      type: 'number',
      'aria-labelledby': 'input-slider',
    }}
  >
    {() => <Input className={classes.input} disableUnderline />}
  </InputMask>
 )
QasimTalkin commented 2 years ago

Any update on this one?

alexandrehpiva commented 2 years ago

With the 3.0.0 alpha version (yarn add react-input-mask@next) you can use the new property beforeMaskedStateChange to write a custom format function:

/**
 * Format to Brazilian currency
 */
export const maskToCurrency = ({ nextState }) => {
  const { value } = nextState || {}

  let amountFormatted = value?.replace?.(/\D/g, '')
  amountFormatted = amountFormatted?.replace?.(/^0+/g, '')

  if (amountFormatted?.length === 2) {
    return {
      ...nextState,
      value: `R$ ${amountFormatted}`,
      selection: {
        start: amountFormatted.length + 3,
        end: amountFormatted.length + 3
      }
    }
  }

  const amountFormattedWithComma = amountFormatted?.replace?.(
    /(?=\d{2})(\d{2})$/,
    ',$1'
  )
  const amountFormattedWithDot = amountFormattedWithComma?.replace?.(
    /(\d)(?=(\d{3})+(?!\d))/g,
    '$1.'
  )

  if (amountFormattedWithDot) {
    return {
      ...nextState,
      value: `R$ ${amountFormattedWithDot}`,
      selection: {
        start: amountFormattedWithDot.length + 3,
        end: amountFormattedWithDot.length + 3
      }
    }
  }

  return nextState
}

And in your React component:

<InputMask
  mask="R$ 9999999999" // Don't use dots and commas
  alwaysShowMask={false}
  beforeMaskedStateChange={maskToCurrency}
  value={value}
  onChange={(e) => setValue(e.target.value)}
>
  <YourInputComponent type="text" name="amount" {...otherProps} />
</InputMask>

Example of the masked value: R$ 12.345,67

luane-aquino commented 10 months ago
<InputMask
  mask="R$ 9999999999" // Don't use dots and commas
  alwaysShowMask={false}
  beforeMaskedStateChange={maskToCurrency}
  value={value}
  onChange={(e) => setValue(e.target.value)}
>
  <YourInputComponent type="text" name="amount" {...otherProps} />
</InputMask>

Hi @alexandrehpiva it is not working or maybe I am missing something, could you provide a working example on codesandbox please? thank you 🙂

nathanpower commented 9 months ago

In case it's of use to anyone, I had to implement number formatting (not currency, the currency symbol is shown outside the input) so I made a reusable hook

export const useFormattedIntegerMask = ({
  maxDigits,
}: {
  maxDigits: number;
}): { mask: Mask; unformat: (string) => number } => {
  let highestNum = 1;
  new Array(maxDigits).fill(0).forEach(() => (highestNum = highestNum * 10));
  const maskLength = new Intl.NumberFormat().format(highestNum).length - 1;
  const mask = new Array(maskLength).fill("9").join("");
  const separator = new Intl.NumberFormat().formatToParts(1000)[1].value;
  return {
    unformat: (str) => {
      return parseInt(str.replaceAll(separator, ""));
    },
    mask: {
      mask,
      maskPlaceholder: "",
      beforeMaskedStateChange: ({ previousState, currentState, nextState }) => {
        const newValue = nextState.value.length
          ? new Intl.NumberFormat().format(parseInt(nextState.value))
          : nextState.value;

        const extraCharCount =
          Number.isInteger(previousState?.selection?.start) &&
          newValue.length > 3 &&
          currentState.selection.start < previousState.selection.start
            ? currentState.selection.start - previousState.selection.start
            : newValue.slice(0, currentState.selection.start).split(separator).length - 1;

        return {
          ...nextState,
          value: newValue,
          selection: {
            start: nextState.selection.start + extraCharCount,
            end: nextState.selection.end + extraCharCount,
          },
        };
      },
    },
  };
};

Note that this assumes no decimal points as our input is for integers only