Skip to content

Commit

Permalink
feat: allow undefined value to e.g. show a placeholder
Browse files Browse the repository at this point in the history
* feat: allow undefined value to e.g. show a placeholder

* chore: remove defaultvalue for demo scene
  • Loading branch information
saschb2b authored Nov 7, 2024
1 parent 18269c6 commit 5af8c03
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 169 deletions.
64 changes: 32 additions & 32 deletions lib/components/CurrencyTextField/CurrencyTextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import TextField, { type TextFieldProps } from '@mui/material/TextField';
* Extends Material UI's TextFieldProps, excluding 'onChange' and 'defaultValue'.
*/
interface CurrencyTextFieldProps
extends Omit<TextFieldProps, 'onBlur' | 'onChange' | 'defaultValue'> {
extends Omit<
TextFieldProps,
'onBlur' | 'onChange' | 'defaultValue' | 'type'
> {
/**
* The character used as the decimal separator.
* Defaults to '.'.
Expand Down Expand Up @@ -102,32 +105,28 @@ export const CurrencyTextField: React.FC<CurrencyTextFieldProps> = ({
...props
}) => {
// Internal state to manage the input value.
const [internalValue, setInternalValue] = useState<Dinero.Dinero>(
(value || defaultValue) ??
Dinero({
amount: 0,
currency: currency,
precision: precision,
}),
const [internalValue, setInternalValue] = useState<Dinero.Dinero | undefined>(
value ?? defaultValue ?? undefined,
);

/**
* TODO: fix loop when precision changes
*/
useEffect(() => {
setInternalValue((prevState) => ({
...prevState,
currency: currency,
precision: precision,
}));
}, [currency, precision]);
if (internalValue) {
const updatedValue = Dinero({
amount: internalValue.getAmount(),
currency,
precision,
});

// Update only if there's a real change to avoid unnecessary re-renders
if (!updatedValue.equalsTo(internalValue)) {
setInternalValue(updatedValue);
}
}
}, [currency, internalValue, precision]);

/**
* Update the internal value when the value prop changes.
* This is necessary to keep the input value in sync with the value prop.
*/
// Update internal value only if `value` prop changes and is defined
useEffect(() => {
if (value) {
if (value !== undefined) {
setInternalValue(value);
}
}, [value]);
Expand All @@ -149,9 +148,8 @@ export const CurrencyTextField: React.FC<CurrencyTextFieldProps> = ({
precision: precision,
});

if (!dineroValue.equalsTo(internalValue)) {
if (!internalValue || !dineroValue.equalsTo(internalValue)) {
onChange?.(dineroValue, values.formattedValue);

setInternalValue(dineroValue);
}
};
Expand All @@ -165,7 +163,7 @@ export const CurrencyTextField: React.FC<CurrencyTextFieldProps> = ({
event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
if (onBlur) {
onBlur(event, internalValue);
onBlur(event, internalValue as Dinero.Dinero);
}
};

Expand All @@ -181,18 +179,20 @@ export const CurrencyTextField: React.FC<CurrencyTextFieldProps> = ({
* Formats the dinero value to a matching number that can be use in NumericFormat
*/
const formattedValue = useMemo(() => {
const amount = internalValue?.getAmount() ?? 0;
const precision = internalValue?.getPrecision() ?? 2;
const factor = Math.pow(10, precision);
if (!internalValue) return ''; // Return empty string if no value
const amount = internalValue.getAmount();
const factor = Math.pow(10, internalValue.getPrecision());
return (amount / factor).toFixed(precision);
}, [internalValue]);
}, [internalValue, precision]);

/**
* Formats the dinero value to a matching number that can be use in NumericFormat
*/
const formattedDefaultValue = useMemo(() => {
const amount = defaultValue?.getAmount() ?? 0;
const precision = defaultValue?.getPrecision() ?? 2;
if (!defaultValue) return '';

const amount = defaultValue.getAmount() ?? 0;
const precision = defaultValue.getPrecision() ?? 2;
const factor = Math.pow(10, precision);
return (amount / factor).toFixed(precision);
}, [defaultValue]);
Expand All @@ -215,7 +215,7 @@ export const CurrencyTextField: React.FC<CurrencyTextFieldProps> = ({
onFocus={handleFocus}
onValueChange={handleValueChange}
{...props}
type={'text'}
type="text"
/>
);
};
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
"devDependencies": {
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"@mui/material": "^5.16.4",
"@mui/styles": "^5.16.4",
"@mui/material": "^5.16.7",
"@mui/styles": "^5.16.7",
"@types/dinero.js": "^1.9.4",
"@types/node": "^20.14.6",
"@types/react": "^18.2.66",
Expand All @@ -57,11 +57,11 @@
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"prettier": "^3.3.2",
"prettier": "3.3.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "^5.2.2",
"vite": "^5.2.0",
"vite": "^5.4.10",
"vite-plugin-dts": "^4.0.1"
},
"dependencies": {
Expand Down
26 changes: 25 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export const App: React.FC = () => {
const [brutto, setBrutto] = React.useState<Dinero.Dinero>(
Dinero({ amount: 5000, currency: 'EUR', precision: 2 }),
);
const [addBrutto, setAddBrutto] = React.useState<Dinero.Dinero | undefined>(
undefined,
);

return (
<Box
Expand All @@ -27,6 +30,25 @@ export const App: React.FC = () => {
setBrutto(value);
}}
/>
<CurrencyTextField
label={'Zusätzliches Einkommen'}
name={'add-einkommen'}
value={addBrutto}
variant={'outlined'}
currencySymbol="€"
currency="EUR"
precision={2}
minimumValue={0}
InputLabelProps={{
shrink: true,
}}
decimalCharacter=","
digitGroupSeparator="."
placeholder="0.00"
onChange={(value) => {
setAddBrutto(value);
}}
/>
<Button
variant="contained"
color="primary"
Expand All @@ -47,7 +69,9 @@ export const App: React.FC = () => {
>
Auf 50 € setzen
</Button>
<Typography>Dinero amount: {brutto.getAmount()}</Typography>
<Typography>
Dinero amount: {brutto.getAmount() + (addBrutto?.getAmount() || 0)}
</Typography>
</Box>
);
};
Loading

0 comments on commit 5af8c03

Please sign in to comment.