Prefix not allowing to show value on NumericFormat with usage of MUI TextField

361 Views Asked by At

NumericFormat is used with MUI TextField and prefix prop. When I start writing any number, I cannot write more than one number. If I write fast this time I can write all numbers but the prefix is gone. All numbers inserted from the keyboard should be shown, and shouldn't be any missing numbers at the start the prefix should be visible but this is not working.

import { Observer } from "mobx-react-lite";
import { TextField, TextFieldProps } from "@mui/material";
import { AllowedNames } from "../utils/utils";
import { useDialogState } from "../states/dialog-state";
import { NumericFormat, NumericFormatProps } from "react-number-format";
import React from "react";

interface NumericFormatCustomProps {
  onChange: (event: { target: { name: string; value: string } }) => void;
  name: string;
}

const NumericFormatCustom = React.forwardRef<
  NumericFormatProps<HTMLInputElement>,
  NumericFormatCustomProps & NumericFormatProps<HTMLInputElement>
>(function NumericFormatCustom(props, ref) {
  const { onChange, prefix, suffix, ...other } = props;

  return (
    <NumericFormat
      {...other}
      getInputRef={ref}
      onValueChange={(values) => {
        onChange({
          target: {
            name: props.name,
            value: values.value,
          },
        });
      }}
      prefix={prefix}
      suffix={suffix}
    />
  );
});

function getValue(value: any, coefficient: number) {
  if (value === "" || value === null || value === undefined) {
    return "";
  }
  return ((value ?? 0) / Math.pow(10, coefficient)).toString();
}

function setValue(value: any, coefficient: number, decimalPlaces: number) {
  if (value === "" || value === null || value === undefined) {
    return null;
  }
  return (
    (Math.round(parseFloat(value) * Math.pow(10, decimalPlaces)) /
      Math.pow(10, decimalPlaces)) *
    Math.pow(10, coefficient)
  );
}

export type FormNumberFieldProps<T extends object> = Omit<
  TextFieldProps,
  "defaultValue" | "type"
> & {
  dataKey: AllowedNames<T, number>;
  prefix?: string;
  suffix?: string;
  decimalPlaces?: number;
  coefficient?: number;
  thousandSeparator?: boolean;
  min?: number;
  max?: number;
};

export default function FormNumberField<T extends object>({
  dataKey,
  prefix = "$ ",
  suffix = "",
  decimalPlaces = 0,
  coefficient = 0,
  thousandSeparator = true,
  min,
  max,
  ...rest
}: FormNumberFieldProps<T>) {
  const [internalError, setInternalError] = React.useState("");
  const stateProvider = useDialogState<T>();
  const state = stateProvider.state;      

  return (
    <Observer>
      {() => {
const value = getValue(state.getValue(dataKey) as any, coefficient);

        const checkMinMax = () => {
          const numberValue = parseInt(value, 10);

          if (typeof min === "number") {
            if (numberValue < min) {
              setInternalError(`The value should be more than ${min}`);
              return;
            }
          }

          if (typeof max === "number") {
            if (numberValue > max) {
              setInternalError(`The value should be less than ${max}`);
              return;
            }
          }

          setInternalError("");
        };

        return (
          <TextField
            {...rest}
            fullWidth
            inputMode="numeric"
            onBlur={checkMinMax}
            value={value}
            onChange={(e) => {
              state.setValue(
                dataKey,
                setValue(e.target.value, coefficient, decimalPlaces) as any,
              );
            }}
            error={!!internalError || !!state.errorOf(dataKey)}
            helperText={internalError || state.errorOf(dataKey)}
            InputLabelProps={{ shrink: true }}
            InputProps={{
              inputComponent: NumericFormatCustom as any,
              inputProps: {
                prefix,
                suffix,
                thousandSeparator,
              },
            }}
          />
        );
      }}
    </Observer>
  );
}
1

There are 1 best solutions below

0
Mümin Celal Pinar On
import { Observer } from "mobx-react-lite";
import { TextField, TextFieldProps } from "@mui/material";
import { AllowedNames } from "../utils/utils";
import { useDialogState } from "../states/dialog-state";
import { NumericFormat, NumericFormatProps } from "react-number-format";
import React from "react";

interface NumericFormatCustomProps {
  onChange: (event: { target: { name: string; value: string } }) => void;
  name: string;
}

const NumericFormatCustom = React.forwardRef<
  NumericFormatProps<HTMLInputElement>,
  NumericFormatCustomProps & NumericFormatProps<HTMLInputElement>
>(function NumericFormatCustom(props, ref) {
  const { onChange, prefix, suffix, ...other } = props;

  return (
    <NumericFormat
      {...other}
      getInputRef={ref}
      onValueChange={(values) => {
        onChange({
          target: {
            name: props.name,
            value: values.value,
          },
        });
      }}
      prefix={prefix}
      suffix={suffix}
    />
  );
});

function getValue(value: any, coefficient: number) {
  if (value === "" || value === null || value === undefined) {
    return "";
  }
  return ((value ?? 0) / Math.pow(10, coefficient)).toString();
}

function setValue(value: any, coefficient: number, decimalPlaces: number) {
  if (value === "" || value === null || value === undefined) {
    return null;
  }
  return (
    (Math.round(parseFloat(value) * Math.pow(10, decimalPlaces)) /
      Math.pow(10, decimalPlaces)) *
    Math.pow(10, coefficient)
  );
}

export type FormNumberFieldProps<T extends object> = Omit<
  TextFieldProps,
  "defaultValue" | "type"
> & {
  dataKey: AllowedNames<T, number>;
  prefix?: string;
  suffix?: string;
  decimalPlaces?: number;
  coefficient?: number;
  thousandSeparator?: boolean;
  min?: number;
  max?: number;
};

export default function FormNumberField<T extends object>({
  dataKey,
  prefix = "$ ",
  suffix = "",
  decimalPlaces = 0,
  coefficient = 0,
  thousandSeparator = true,
  min,
  max,
  ...rest
}: FormNumberFieldProps<T>) {
  const [internalError, setInternalError] = React.useState("");
  const stateProvider = useDialogState<T>();
  const state = stateProvider.state;

  // I moved here
  const value = getValue(state.getValue(dataKey) as any, coefficient);

  return (
    <Observer>
      {() => {
        const checkMinMax = () => {
          const numberValue = parseInt(value, 10);

          if (typeof min === "number") {
            if (numberValue < min) {
              setInternalError(`The value should be more than ${min}`);
              return;
            }
          }

          if (typeof max === "number") {
            if (numberValue > max) {
              setInternalError(`The value should be less than ${max}`);
              return;
            }
          }

          setInternalError("");
        };

        return (
          <TextField
            {...rest}
            fullWidth
            inputMode="numeric"
            onBlur={checkMinMax}
            value={value}
            onChange={(e) => {
              state.setValue(
                dataKey,
                setValue(e.target.value, coefficient, decimalPlaces) as any,
              );
            }}
            error={!!internalError || !!state.errorOf(dataKey)}
            helperText={internalError || state.errorOf(dataKey)}
            InputLabelProps={{ shrink: true }}
            InputProps={{
              inputComponent: NumericFormatCustom as any,
              inputProps: {
                prefix,
                suffix,
                thousandSeparator,
              },
            }}
          />
        );
      }}
    </Observer>
  );
}