How to add thousand separator to Textfield? - reactjs

I've tried to add thousand separator by using react-number-format package. However, I couldn't make it happen. I use react 18. And this what I tried:
function NumberFormatCustom(props) {
const { inputRef, onChange, ...other } = props;
return (
<NumericFormat
{...other}
getInputRef={inputRef}
onValueChange={(values) => {
onChange({
target: {
name: props.name,
value: values.value,
},
});
}}
thousandSeparator
/>
);
}
<TextField
required
error={loanType === "I" && totalAmount > 100000}
fullWidth
type="number"
label="Tota lAmount"
value={totalAmount}
onChange={(e) => setTotalAmount(e.target.value)}
InputProps={{
inputComponent: NumberFormatCustom,
startAdornment: (
<InputAdornment position="start">$</InputAdornment>
),
}}
/>

According to the current docs you can use react-number-format along with MUI TextField like this:
import { NumericFormat } from 'react-number-format';
import { TextField } from '#mui/material';
<NumericFormat value={12323} customInput={TextField} />;
In your case, your code can be like this:
import { InputAdornment, TextField } from "#mui/material";
import { useState } from "react";
import { NumericFormat } from "react-number-format";
const MyNumberComponent = () => {
const [totalAmount, setTotalAmount] = useState(52100);
const handleChange = (ev) => {
setTotalAmount(ev.floatValue);
};
const materialUiTextFieldProps = {
required: true,
error: totalAmount > 100000,
fullWidth: true,
label: "Total Amount",
InputProps: {
startAdornment: <InputAdornment position="start">$</InputAdornment>
}
};
return (
<>
<NumericFormat
value={totalAmount}
customInput={TextField}
onValueChange={handleChange}
thousandSeparator=","
decimalSeparator="."
{...materialUiTextFieldProps}
/>
binded value: {totalAmount}
</>
);
};
export default MyNumberComponent;
You can take a look at this sandbox for a live working example of this approach.

I am using this, it works well ->
export const numberWithCommas = (x: number) =>
x?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') || '0';
And to change it back to number:
export const numberWithoutCommas = (x: number | string, isFloat?: boolean) => {
const str = x.toString().replace(/,/g, '');
return isFloat ? parseFloat(str) : parseInt(str, 10);
};

Related

Getting Invalid time value when i try to type date

// using this code blow, getting error when trying to type date. Getting Invalid time
//value
//when i try to type date
import { TextField } from '#mui/material';
import { DateTimePicker } from '#mui/x-date-pickers';
import { useInput } from 'react-admin';
import { useWatch } from 'react-hook-form';
import { parseISO, formatISO } from 'date-fns';
const AMCDateInput = ({ source, label, minSource, maxSource, ...rest }) => {
const { id, field, fieldState, formState, isRequired } = useInput({
source,
format: (v) => {
return v ? parseISO(v) : null;
},
parse: (v) => {
return formatISO(v);
},
});
const minBound = useWatch({ name: minSource, exact: true });
const maxBound = useWatch({ name: maxSource, exact: true });
return (
<>
<DateTimePicker
renderInput={(props) => <TextField fullWidth {...props} />}
label={label}
minutesStep={5}
minDateTime={minBound?.length > 0 ? parseISO(minBound) : null}
maxDateTime={maxBound?.length > 0 ? parseISO(maxBound) : null}
ampm={false}
{...field}
{...rest}
/>
</>
);
};
export default AMCDateInput;
//getting error

react-select: Impossible to get value onBlur event

I have a problem with the react-select lib (see here: https://www.npmjs.com/package/react-select). For the validation of my form, I display an error message on the onBlur event. The problem is that no value appears in my logs.
However, onChange works fine.
Handler
const handleBlur = (e: FocusEvent<HTMLInputElement, Element>) => {
//Here, When I select a value and deselect the input, no value exists in this log.
console.log("value Select: ", e.target.value )
}
The return of my component function
<Select
placeholder={`Select ${name}`}
name={name}
id={id}
onBlur={(e) => {
handleBlur(e)
}}
onChange={(e) => {
setValueOnChange(e, name)
}}
options={options}
styles={customStyle}
/>
Anyone have a suggestion?
Thanks !
import "./styles.css";
import Select from "react-select";
import { useState } from "react";
export default function App() {
const options = [
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
];
const [value, setValue] = useState();
const [focusValue, setFocusValue] = useState();
const handleChange = (changeValue) => {
setValue(changeValue);
};
const handleFocus = (event) => {
const focusValue = event.target.value;
console.log("Should be focus value", focusValue);
setFocusValue(focusValue);
};
const handleBlur = (event) => {
const blurValue = event.target.value;
console.log("Should be blur value", blurValue);
if (focusValue !== blurValue) {
console.log("Do something");
}
};
return (
<div className="App">
<h1>React Select onFocus & onBlur </h1>
<Select
options={options}
value={value}
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
/>
</div>
);
}

How to create forms with MUI (material ui)

Hiho,
I want to use mui in my current react project. Is there a different/better way to create forms than the following example?:
const [companyName, setCompanyName] = useState<string>("");
const [companyNameError, setCompanyNameError] = useState<boolean>(false);
const changeName = (event: React.ChangeEvent<HTMLInputElement>) => {
if(event.target.value === "") {
setCompanyNameError(true);
} else {
setCompanyNameError(false);
}
event.preventDefault();
setCompanyName(event.target.value);
}
const anyInputFieldEmpty = () => {
var result = false;
if(companyName === "") {
setCompanyNameError(true);
result = true;
} else {
setCompanyNameError(false);
}
// add the same check for all other fields. My real code has multiple input fields
return result;
}
const resetFields = () => {
setCompanyName("");
}
return (
<div>
<TextField
required
fullWidth
label="Company Name"
margin="dense"
name="companyName"
value={companyName}
onChange={changeName}
helperText={companyNameError ? "Company name is not allowed to be empty!" : ""}
error={companyNameError}
/>
<Button
sx={{ alignSelf: 'center', }}
variant="contained"
onClick={() => {
if(!anyInputFieldEmpty()) {
onSubmitClick(); // some function from somewhere else, which triggers logic
resetFields(); // This form is in a popover. The values should be resetted before the user open it again.
}
}}
>
Create
</Button>
</div>);
It feels wrong to do the validation this way if I use multiple textfields (up to 9). Its a lot of boilerplate code and if I add further validation rules (for example a minimum character count) it goes crazy.
Is this the right way to achive my goal?
T
As others have mentioned. Formik and Yup works great for validation. Formik also provides a way to easily disable your submit buttons. Here is a codesandbox : https://codesandbox.io/s/long-butterfly-seogsw?file=/src/App.js
I think you should check the Formik for hook-based validation and jsonforms, react-declarative from json-schema based view creation
Less code solutions is better on production, but for a learning reason it better to write real code based on hooks, contexts or redux reducers
import { useState } from "react";
import { One, FieldType } from "react-declarative";
const fields = [
{
type: FieldType.Text,
title: "First name",
defaultValue: "Peter",
name: "firstName"
},
{
type: FieldType.Text,
title: "Age",
isInvalid: ({ age }) => {
if (age.length === 0) {
return "Please type your age";
} else if (parseInt(age) === 0) {
return "Age must be greater than zero";
}
},
inputFormatterTemplate: "000",
inputFormatterAllowed: /^[0-9]/,
name: "age"
}
];
export const App = () => {
const [data, setData] = useState(null);
const handleChange = (data) => setData(data);
return (
<>
<One fields={fields} onChange={handleChange} />
<pre>{JSON.stringify(data, null, 2)}</pre>
</>
);
};
export default App;
An example project could be found on this codesandbox
MUI does not have a native form validator
i recommend using react-hook-form + yup it's pretty simple and has a lot of tutorials
https://react-hook-form.com/get-started
EXEMPLE
TextFieldComponent
import { OutlinedTextFieldProps } from '#mui/material';
import React from 'react';
import { Control, useController } from 'react-hook-form';
import { InputContainer, TextFieldStyled } from './text-field.styles';
export interface TextFieldProps extends OutlinedTextFieldProps {
control: Control;
helperText: string;
name: string;
defaultValue?: string;
error?: boolean;
}
export const TextField: React.FC<TextFieldProps> = ({
control,
helperText,
name,
defaultValue,
error,
...rest
}) => {
const { field } = useController({
name,
control,
defaultValue: defaultValue || ''
});
return (
<InputContainer>
<TextFieldStyled
helperText={helperText}
name={field.name}
value={field.value}
onChange={field.onChange}
fullWidth
error={error}
{...rest}
/>
</InputContainer>
);
};
Styles
import { TextField } from '#mui/material';
import { styled } from '#mui/material/styles';
export const TextFieldStyled = styled(TextField)`
.MuiOutlinedInput-root {
background: ${({ theme }) => theme.palette.background.paper};
}
.MuiInputLabel-root {
color: ${({ theme }) => theme.palette.text.primary};
}
`;
export const InputContainer = styled('div')`
display: grid;
grid-template-columns: 1fr;
align-items: center;
justify-content: center;
width: 100%;
`;
export const InputIconStyled = styled('i')`
text-align: center;
`;
Usage
// Validator
const validator = yup.object().shape({
email: yup
.string()
.required(translate('contact.email.modal.email.required'))
.email(translate('contact.email.modal.email.invalid')),
});
// HookForm
const {
control,
handleSubmit,
formState: { errors }
} = useForm({
resolver: yupResolver(validator)
});
// Compoenent
<TextField
label="label"
fullWidth
placeholder="placeholder
size="small"
control={control}
helperText={errors?.name?.message}
error={!!errors?.name}
name={'name'}
variant={'outlined'}
/>

Ant Design & React Testing Library - Testing Form with Select

I'm attempting to test a Select input inside an Ant Design Form filled with initialValues and the test is failing because the Select does not receive a value. Is there a best way to test a "custom" rendered select?
Test Output:
Error: expect(element).toHaveValue(chocolate)
Expected the element to have value:
chocolate
Received:
Example Test:
import { render, screen } from '#testing-library/react';
import { Form, Select } from 'antd';
const customRender = (ui: React.ReactElement, options = {}) => render(ui, {
wrapper: ({ children }) => children,
...options,
});
describe('select tests', () => {
it('renders select', () => {
const options = [
{ label: 'Chocolate', value: 'chocolate' },
{ label: 'Strawberry', value: 'strawberry' },
{ label: 'Vanilla', value: 'vanilla' },
];
const { value } = options[0];
customRender(
<Form initialValues={{ formSelectItem: value }}>
<Form.Item label="Form Select Label" name="formSelectItem">
<Select options={options} />
</Form.Item>
</Form>,
);
expect(screen.getByLabelText('Form Select Label')).toHaveValue(value);
});
});
testing a library component may be harsh sometimes because it hides internal complexity.
for testing antd select i suggest to mock it and use normal select in your tests like this:
jest.mock('antd', () => {
const antd = jest.requireActual('antd');
const Select = ({ children, onChange, ...rest }) => {
return <select role='combobox' onChange={e => onChange(e.target.value)}>
{children}
</select>;
};
Select.Option = ({ children, ...otherProps }) => {
return <option role='option' {...otherProps}}>{children}</option>;
}
return {
...antd,
Select,
}
})
this way you can test the select component as a normal select (use screen.debug to check that the antd select is mocked)
I mocked a normal select and was able to get everything working.
The following example utilizes Vitest for a test runner but should apply similar to Jest.
antd-mock.tsx
import React from 'react';
import { vi } from 'vitest';
vi.mock('antd', async () => {
const antd = await vi.importActual('antd');
const Select = props => {
const [text, setText] = React.useState('');
const multiple = ['multiple', 'tags'].includes(props.mode);
const handleOnChange = e => props.onChange(
multiple
? Array.from(e.target.selectedOptions)
.map(option => option.value)
: e.target.value,
);
const handleKeyDown = e => {
if (e.key === 'Enter') {
props.onChange([text]);
setText('');
}
};
return (
<>
<select
// add value in custom attribute to handle async selector,
// where no option exists on load (need to type to fetch option)
className={props.className}
data-testid={props['data-testid']}
data-value={props.value || undefined}
defaultValue={props.defaultValue || undefined}
disabled={props.disabled || undefined}
id={props.id || undefined}
multiple={multiple || undefined}
onChange={handleOnChange}
value={props.value || undefined}
>
{props.children}
</select>
{props.mode === 'tags' && (
<input
data-testid={`${props['data-testid']}Input`}
onChange={e => setText(e.target.value)}
onKeyDown={handleKeyDown}
type="text"
value={text}
/>
)}
</>
);
};
Select.Option = ({ children, ...otherProps }) => (
<option {...otherProps}>{children}</option>
);
Select.OptGroup = ({ children, ...otherProps }) => (
<optgroup {...otherProps}>{children}</optgroup>
);
return { ...antd, Select };
});
utils.tsx
import { render } from '#testing-library/react';
import { ConfigProvider } from 'antd';
const customRender = (ui: React.ReactElement, options = {}) => render(ui, {
wrapper: ({ children }) => <ConfigProvider prefixCls="bingo">{children}</ConfigProvider>,
...options,
});
export * from '#testing-library/react';
export { default as userEvent } from '#testing-library/user-event';
export { customRender as render };
Select.test.tsx
import { Form } from 'antd';
import { render, screen, userEvent } from '../../../test/utils';
import Select from './Select';
const options = [
{ label: 'Chocolate', value: 'chocolate' },
{ label: 'Strawberry', value: 'strawberry' },
{ label: 'Vanilla', value: 'vanilla' },
];
const { value } = options[0];
const initialValues = { selectFormItem: value };
const renderSelect = () => render(
<Form initialValues={initialValues}>
<Form.Item label="Label" name="selectFormItem">
<Select options={options} />
</Form.Item>
</Form>,
);
describe('select tests', () => {
it('renders select', () => {
render(<Select options={options} />);
expect(screen.getByRole('combobox')).toBeInTheDocument();
});
it('renders select with initial values', () => {
renderSelect();
expect(screen.getByLabelText('Label')).toHaveValue(value);
});
it('handles select change', () => {
renderSelect();
expect(screen.getByLabelText('Label')).toHaveValue(value);
userEvent.selectOptions(screen.getByLabelText('Label'), 'vanilla');
expect(screen.getByLabelText('Label')).toHaveValue('vanilla');
});
});

set State doesn't update the value

I have a problem with set State in dropdown semantic-ui-react.
I am using typescript in my code.
The selected category value doesn't change and always returns an empty string "". How can I fix this?
import debounce from "lodash.debounce";
import { observer } from "mobx-react-lite";
import React, { SyntheticEvent, useContext, useState } from "react";
import {
Dropdown,
DropdownItemProps,
DropdownProps,
Form,
InputOnChangeData,
Popup,
} from "semantic-ui-react";
import { RootStoreContext } from "../../../app/stores/rootStore";
const regex = new RegExp("^[a-zA-Z0-9 ]+$");
interface IProps {
loading: boolean;
}
const PurchaseDetailsFilter: React.FC<IProps> = ({ loading }) => {
const rootStore = useContext(RootStoreContext);
const {
setFilter,
itemCount,
loadPurchaseDetails,
categoryId,
setCategoryId,
} = rootStore.purchaseDetailStore;
const { purchaseCategories } = rootStore.purchaseCategoryStore;
const purchaseCategoriesList: any = purchaseCategories.map((data) => {
return { key: data.id, text: data.name, value: data.id };
});
const categoryOptions: DropdownItemProps[] = [
{ key: "all", value: "all", text: "All" },
].concat(purchaseCategoriesList);
const [selectedCategory, setSelectedCategory] = useState("");
const [filterValid, setFilterValid] = useState(true);
const f = debounce((value: string) => {
if (value !== "" && !regex.test(value)) {
setFilterValid(false);
setFilter(value);
} else {
setFilterValid(true);
console.log(loading);
setFilter(value);
loadPurchaseDetails();
}
}, 500);
const cat = debounce((value: string) => {
console.log(value);
setSelectedCategory(value as string);
console.log(selectedCategory);
setCategoryId((value ==="all" ? "" : value) as string);
loadPurchaseDetails();
}, 500);
const handleOnChange = (
event: React.ChangeEvent<HTMLInputElement>,
{ value }: InputOnChangeData
) => {
f(value);
};
let popupMessage = "";
if (!filterValid) {
popupMessage = "Invalid character.";
} else if (itemCount === 0) {
popupMessage = "No results found.";
}
const handleSelectedCategory = (
event: SyntheticEvent<HTMLElement>,
{value}: DropdownProps
) => {
console.log(value);
setSelectedCategory(String(value));
console.log(selectedCategory);
setCategoryId((value ==="all" ? "" : value) as string);
loadPurchaseDetails();
};
return (
<Form>
<Form.Group>
<Form.Field>
<Popup
trigger={
<Form.Input
placeholder={"Enter a filter."}
name={"filter"}
error={!filterValid}
label={"Filter"}
onChange={handleOnChange}
icon={"search"}
loading={loading}
/>
}
content={popupMessage}
on={"click"}
open={!filterValid || itemCount === 0}
position={"right center"}
/>
</Form.Field>
<Form.Field style={{ marginLeft: "10em" }}>
<label>Category</label>
<Dropdown
//defaultValue="All"
//value={categoryId}
value={selectedCategory}
onChange={handleSelectedCategory}
//defaultValue={categoryOptions[0].value}
placeholder="Choose an category"
options={categoryOptions}
selection
/>
</Form.Field>
<Form.Field>
<label>Project</label>
<Dropdown placeholder="Choose an option" />
</Form.Field>
<Form.Field>
<label>Supplier</label>
<Dropdown placeholder="Choose an option" />
</Form.Field>
</Form.Group>
</Form>
);
};
export default observer(PurchaseDetailsFilter);
maybe what you are looking for is this one:
const handleSelectedCategory = (
event: SyntheticEvent<HTMLElement>,
{value}: DropdownProps
) => {
console.log(value);
setSelectedCategory(String(value));
console.log(selectedCategory);
setCategoryId(String(value ==="all" ? "" : value));
loadPurchaseDetails();
};
notice that I changed value as string to be String(value).
I once fiddle around with TypeScript, but I forget how as string casting works. You might find better explanation here: https://stackoverflow.com/a/32607656/7467018
Have a look at the below code comment
const handleSelectedCategory = (
event: SyntheticEvent<HTMLElement>,
{ value }: DropdownProps
) => {
console.log(value); // If this value is string i.e "Hello World", then just call setSelectedCategory(value);
setSelectedCategory(value as string); // Not required. Just call setSelectedCategory(value) if value is string.
console.log(selectedCategory);
setCategoryId((value === "all" ? "" : value) as string); // Curious, where is setCategoryId froming from?
loadPurchaseDetails();
};

Resources