Mui Select not changing value when defaultValue is set - reactjs

i have a select compontent from the mui react libary.
I also use react-hook-form with controller.
In the controller i will set defaultValue becouse i want to use the reset function von react-hook-form. But when i set default value i cant change the value. When there is no default value everthing works as expected.
Here my Code:
import * as React from "react";
import { useTheme } from "#mui/material/styles";
import OutlinedInput from "#mui/material/OutlinedInput";
import InputLabel from "#mui/material/InputLabel";
import MenuItem from "#mui/material/MenuItem";
import FormControl from "#mui/material/FormControl";
import Select from "#mui/material/Select";
import { useFormContext, Controller } from "react-hook-form";
import Box from '#mui/material/Box';
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250,
},
},
};
function getStyles(name, personName, theme) {
return {
fontWeight:
personName.indexOf(name) === -1
? theme.typography.fontWeightRegular
: theme.typography.fontWeightMedium,
};
}
function MySelect({ formname, name, reset, setSelected, data = [] }) {
const {
control,
formState: { errors },
setValue,
} = useFormContext();
let isError = false;
let errorMessage = "";
if (errors && errors.hasOwnProperty(name)) {
isError = true;
errorMessage = errors[name].message;
}
const handleChange = (event) => {
const {
target: { value },
} = event;
setSelected(
// On autofill we get a stringified value.
typeof value === "string" ? value.split(",") : value
);
};
return (
<Box sx={{minWidth: 120}}>
<InputLabel id="demo-multiple-name-label">{name}</InputLabel>
<Controller
name={formname}
style={{ width: 200 }}
control={control}
defaultValue=""
render={({ field: { onChange, value } }) => (
<Select
sx={{ width: 200 }}
labelId="demo-multiple-name-label"
id="demo-multiple-name"
multiple={false}
value={value}
onChange={onChange}
input={<OutlinedInput label={name} />}
MenuProps={MenuProps}
error={isError}
helperText={errorMessage}
>
{data.map((element, i) => {
if (element.bsa_maskenkey)
return (
<MenuItem value={element} key={i}>
{`${element.bsa_maskenkey} ${element.we_menge}`}
</MenuItem>
);
else if (element.blager_maskenkey)
return (
<MenuItem value={element} key={i}>
{`${element.blager_maskenkey} ${element.blagerpo_maskenkey} | Menge: ${element.summe}`}
</MenuItem>
);
else
return (
<MenuItem value={element} key={i}>
{`${element.objekt} | Menge: ${element.menge}`}
</MenuItem>
);
})}
</Select>
)}
/>
</Box>
);
}
export default React.memo(MySelect)
```

Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). You should decide between using a controlled or uncontrolled input element and remove one of these props. I assume you need controlled inputs since you probably maintain input value in some type of state of your form.
It should be just enough to declare defaultValues in useForm hook, and those will be automatically bind to their related input fields inside form. For example:
useForm({
mode: 'onSubmit',
reValidateMode: 'onChange',
defaultValues: { FieldA: "foo", FieldB: "xai" },
shouldFocusError: true,
shouldUnregister: false,
shouldUseNativeValidation: false
})
When using like this, those default values for FieldA and FieldB will be automatically set as initial values in their related fields inside form, you just need to be sure that you are properly binding those fields with related form state.

Related

I keep getting violation from chrome console and it does laggy animation when selecting a controlled component with MUI textfield select props

I'm rendering from database over 300 choices to the MUI textfield select. I used the controller component from react hook forms how do I solve this?
import { Menu, MenuItem, TextField } from "#mui/material";
import React from "react";
import { Controller } from "react-hook-form";
import { useSelector } from "react-redux";
const DropdownState = ({ controller, textField, type, isLabelAsId = true }) => {
const position = useSelector((state) => state.position);
let data = [];
if (type === "position") {
data = position.positions.map((x) => ({
id: x.PosID,
label: x.Positn,
}));
} else {
console.error(
'Please choose a type between "position" and "test" '
);
return;
}
if (isLabelAsId) data = data.map((x) => ({ ...x, id: x?.label }));
return (
<Controller
{...controller}
render={({ field, fieldState }) => (
<TextField
select
size="small"
margin="normal"
sx={{
flex: 1,
"& fieldset": {
borderRadius: 3,
},
}}
{...field}
{...textField}
error={!!fieldState.error}
helperText={fieldState.error?.message}
>
{data
.sort((a, b) =>
a.label.toLowerCase() < b.label.toLowerCase()
? -1
: a.label.toLowerCase() > b.label.toLowerCase()
? 1
: 0
)
.map((x, index) => (
<MenuItem key={index} value={x.id}>
{x.label}
</MenuItem>
))}
</TextField>
)}
/>
);
};
export default DropdownState;
What I want is to avoid getting this warning message from the console and stop the laggy animation when clicking the select input field "Position"

How to restrict numbers (even in String type) from MaterialUI TextField?

Consider the following code:
<Grid item lg={6} md={6} xs={12} >
<TextField fullWidth type={'text'} label="Product Color" value={productColor} onChange={(e) => setProductColor(e.target.value)} />
</Grid>
Even if it is set to type text, how do I completely prevent numbers from being entered and only allow text from A-z or a-z? Any Ideas?
Use regex like so:
import { Stack, TextField } from "#mui/material";
import { useState } from "react";
const isLetters = (str) => /^[A-Za-z]*$/.test(str);
export default function IndexPage() {
const [val, setVal] = useState("");
const onInputChange = (e) => {
const { value } = e.target;
if (isLetters(value)) {
setVal(value);
}
};
return (
<Stack spacing={2} padding={2} height={"100vh"}>
<TextField label="Letters only" value={val} onChange={onInputChange} />
</Stack>
);
}
One way would be to to utilise the fact that you can pass in certain props to text fields and combine this with regex. This wouldn't necessarily prevent them from allowing it to be entered, however, it will prevent submission of the text field if it contains any numbers. You would then just need to add error handling to display the associated message
<TextField
inputProps={{ pattern: "([^0-9]*)" }} error={'Error Message'}
/>
Check out the textfield api here for further details
Instead of re-inventing the wheel you could use something like react-number-format.
They have an example in their docs:
<NumberFormat customInput={TextField} format="#### #### #### ####" />
It is also possible to do it the other way around, giving more control over the TextField but needs a bit more work.
import React from "react";
import { InputBaseComponentProps } from "#mui/material";
import NumberFormat from "react-number-format";
export const NumberInput = React.forwardRef<
NumberFormat,
InputBaseComponentProps
>((props, ref) => {
const { onChange, ...other } = props;
return (
<NumberFormat
isNumericString
{...(other as any)}
getInputRef={ref}
onValueChange={(values) =>
onChange && onChange({ target: { value: values.value } } as any)
}
/>
);
});
Then use it like this, note that the price value is still a string.
const [price, setPrice] = useState("0");
return <TextField
value={price}
onChange={(evt) => setPrice(evt.target.value) }
InputProps={{
inputComponent: NumberInput,
inputProps: {
decimalSeparator: ",",
decimalScale: 2,
fixedDecimalScale: true,
allowNegative: false,
prefix: "€ ",
},
}}
This gives you something like this:

How can I remove the default mm/dd/yyyy from TextField in matirial-ui?

I'm using material-ui and I want to make a datetime-picker. If the user doesn't pick anything, I want to be visible only the label "Date", but as you can see in the pictures below, there are some default mm/dd/yyyycharacters. Is there any way I can remove them?
You can hardly see the label because of those characters:
,
After I click, the label is up(what I want), but again, I have mm/dd/yyyy
Here is the code I'm using:
<Grid item>
<TextField
classes={{ root: classes.textFieldRoot }}
name="Date"
label="Date"
type="datetime-local"
/>
</Grid>
And this is what I want to get:
Before user clicks:
After user chooses date and time:
You can handle it by state. When input is on focus or it has value, its type will change to datetime-local input. Otherwise when the input is on blur or has not value its type will change to text for achieving your aim.
import { TextField } from "#material-ui/core";
import { useState } from "react";
import "./styles.css";
export default function App() {
const [focus, setFocused] = useState(false);
const [hasValue, setHasValue] = useState(false);
const onFocus = () => setFocused(true);
const onBlur = () => setFocused(false);
return (
<>
<TextField
onFocus={onFocus}
onBlur={onBlur}
InputProps={{
classes: {
input: "CustomTextField"
}
}}
onChange={(e) => {
if (e.target.value) setHasValue(true);
else setHasValue(false);
}}
label="Date"
type={hasValue || focus ? "datetime-local" : "text"}
/>
</>
);
}
Set a default date to the date component, date should be in the form yyyy-mm-dd.
import { TextField } from "#material-ui/core";
import { useState } from "react";
function getCurrentDate() {
let date = new Date();
let dd = String(date.getDate()).padStart(2, "0");
let mm = String(date.getMonth() + 1).padStart(2, "0");
let yyyy = String(date.getFullYear());
return yyyy + "-" + mm + "-" + dd;
// return a date in the form yyyy-mm-dd
}
export default function App() {
const [date,setDate] = useState(getCurrentDate());
return (
<>
<TextField
value={date}
onChange={(e) => {
setDate(e.target.value);
}}
label="Date"
type={"date"}
/>
</>
);
}

Select with chip input not displaying the selected value

I have a Select and the inputs are in Chip Format. I tried console log of the value selected and it is getting it fine. But for some reason, it does not get displayed on the select box. What am I doing wrong here?
handleChange = event => {
this.setState({ badge : event.target.value });
};
const chipOptions = [
{key: 1, 'text': 'text1', 'value': 'text1'},
{key: 2, 'text':'text2', 'value':'text2'},
{key: 3, 'text':'text3', 'value':'text3'}
]
<FormControl className={classes.formControl}>
<Select
value={this.state.badge}
onChange={this.handleChange}
inputProps={{
name: 'badge',
id: 'badge-simple',
}}
>
{chipOptions && chipOptions.map(option=> (
<Chip key={option.value} label={option.value} className={classes.chip} value={option.value} />
))}
</Select>
</FormControl>
The default manner in which Select renders the selected value is to render its children. In the Select source code as it is looping through the children of the Select, it does the following:
selected = areEqualValues(value, child.props.value);
if (selected && computeDisplay) {
displaySingle = child.props.children;
}
This is based on the assumption of the Select having MenuItem children. For instance, in the following example the first MenuItem would be selected and that MenuItem's children would be the text "Item 1":
<Select value={1}>
<MenuItem value={1}>Item 1</MenuItem>
<MenuItem value={2}>Item 2</MenuItem>
</Select>
Your Chips don't have children, so nothing is displayed. You can customize this behavior by specifying the renderValue property on Select. This is a function that receives the value and can decide what to render.
The following example shows using the renderValue prop to render a Chip:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import FormControl from "#material-ui/core/FormControl";
import Chip from "#material-ui/core/Chip";
import Select from "#material-ui/core/Select";
import { withStyles } from "#material-ui/core/styles";
const styles = theme => ({
formControl: {
margin: theme.spacing.unit,
minWidth: 120
}
});
const chipOptions = [
{ key: 1, text: "text1", value: "text1" },
{ key: 2, text: "text2", value: "text2" },
{ key: 3, text: "text3", value: "text3" }
];
function App({ classes }) {
const [value, setValue] = useState("text1");
const renderChip = value => {
return <Chip label={value} className={classes.chip} />;
};
return (
<>
<FormControl className={classes.formControl}>
<Select
inputProps={{
name: "badge",
id: "badge-simple"
}}
renderValue={renderChip}
value={value}
onChange={event => {
console.log(event.target.value);
setValue(event.target.value);
}}
>
{chipOptions &&
chipOptions.map(option => (
<Chip
key={option.value}
label={option.value}
className={classes.chip}
value={option.value}
/>
))}
</Select>
</FormControl>
</>
);
}
const StyledApp = withStyles(styles)(App);
const rootElement = document.getElementById("root");
ReactDOM.render(<StyledApp />, rootElement);

How do I implement field validation for 'react-select'

I need a simple "required" validation for 'react-select' (github repo).
In the latest version it uses css-in-js approach. So I have custom styles:
export const customStyles = {
control: (base, state) => ({
...base,
}),
menu: (base, state) => ({
...base,
}),
menuList: (base, state) => ({
...base,
}),
}
How can I change e.g. borderColor if field is invalid?
On this point there's an issue opened on GitHub.
I see two different approaches:
the "lazy" one, where you change the border colour by adding a specific className. Example here.
As you want to custom the original select I would recommend to embed your customSelect in a separate file. Then you can pass a props isValid and use it to change your borderColor.
class CustomSelect extends React.Component {
render() {
const {
isValid
} = this.props
const customStyles = {
control: (base, state) => ({
...base,
// state.isFocused can display different borderColor if you need it
borderColor: state.isFocused ?
'#ddd' : isValid ?
'#ddd' : 'red',
// overwrittes hover style
'&:hover': {
borderColor: state.isFocused ?
'#ddd' : isValid ?
'#ddd' : 'red'
}
})
}
return <Select styles={ customStyles } {...this.props}/>
}
}
A help for someone who doesn't want to add all time some codes for only of this required validation in react-select. Just use react-hook-form-input.
<RHFInput
as={<Select options={options} />}
rules={{ required: 'Please select an option'}}
name="reactSelect"
register={register}
setValue={setValue}
/>
Where this RHFInput from react-hook-form-input was just a save for me.. Complete example- react-hook-form-input.
import React from 'react';
import useForm from 'react-hook-form';
import { RHFInput } from 'react-hook-form-input';
import Select from 'react-select';
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
];
function App() {
const { handleSubmit, register, setValue, reset, errors } = useForm();
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<RHFInput
as={<Select options={options} />}
rules={{ required: 'Please select an option'}}
name="reactSelect"
register={register}
setValue={setValue}
/>
<span className="text-danger">
{errors.reactSelect && errors.reactSelect.type === 'required' && "Please select"}
</span>
<button type="button">Reset Form</button>
<button>submit</button>
</form>
);
}
Hope, it will help someone like me as a beginner in react.
render.js
export const renderSelect = (props) => (
<div>
<Select
{...props}
value={props.input.value}
onChange={(value) => props.input.onChange(value)}
onBlur={() => props.input.onBlur(props.input.value)}
options={props.options}
key={props.input.value}
/>
{props.meta.touched && (props.meta.error && <p style={{ color: "red",fontSize:"12px" }}>{props.meta.error}</p>)}
</div>
);
implementForm.js
<Field
name="sex"
component={renderSelect}
options={Status}
isClearable={true}
validate={required}
/>
requiredFileMessage.js
const required = value => value ? undefined : 'Required'
The best way I found is to create a transparent input field that will be queried via javascript standard checkValidity. This should have absolute positioning and 100% width & height. You can then bind a listener to the input field for the invalid event created by checkValidity
Here is the code I use. There are alterations for use of value field as well as some styling (for MDB inputs) , but you can just change the classes of input to match your own styles libraries. In this manner your validation styling will be the same as your existing form inputs.
Hopes this helps someone.
/**************************************************************************************
*** Select - New Select Control Using react-select (to stop stupid probs with MDB) ***
**************************************************************************************/
// Use This progressively instead of InputSelect
/* Differences from native ReactSelect
Validation - Applies transparent input overlay that works with form checkValidity()
value: This is the record.field value not an object {label: x, value: y} as for react-select
grouped: Explicitly set grouped options when set true
objValue: Works the same as react-select value object
*/
// Note: value property works differently do react-select , use ObjValue to work same as react-select
export const Select = (props) => {
let { id, options, cols, value, objValue, label, grouped, ...o } = props
id = id ? id : 'react-select'
const [selected, setSelected] = useState(value)
const [invalid, setInvalid] = useState(false)
//DEFAULTS
if (!grouped) grouped = false
//--------------------------
// VALIDATION EVENT HANDLERS
//--------------------------
useEffect(() => {
//ADD EVENT HANDLER FOR FOR VALIDATION
let ele = document.getElementById(`${id}_invalid}`)
ele.addEventListener('invalid', (e) => {
console.log('e is ', selected, e)
if (typeof selected === 'undefined' || selected !== null) setInvalid(true)
})
//ON COMPONENT EXIT - REMOVE EVENT HANDLER
return () => {
ele.removeEventListener('invalid', () => {
setInvalid(false)
})
}
// eslint-disable-next-line
}, [])
//Value property (Allows Single field assignent) - translates to object in for {label:x, value:y}
useEffect(() => {
let val
if (grouped) {
val = _.findInGroup(options, 'options', (rec) => rec.value === value)
} else {
val = options.find((rec) => rec.value === value)
}
//console.log('Selected==>', val)
setSelected(val)
// eslint-disable-next-line
}, [value, options])
//objValue Property (Emulate standard react-select value object)
useEffect(() => {
if (objValue) {
setSelected(objValue)
}
// eslint-disable-next-line
}, [objValue])
//STYLING SAME AS MDB INPUT COMPONENTS
const customStyles = {
valueContainer: (provided, state) => ({
...provided,
backgroundColor: 'aliceblue',
}),
dropdownIndicator: (provided, state) => ({
...provided,
backgroundColor: 'aliceblue',
}),
}
const handleChange = (opt, i) => {
setSelected(opt)
//Callback function (i is used for nested data in record)
if (props && props.onChange) props.onChange(opt, i)
}
return (
<Col cols={cols}>
{label && <label className='tp-label text-uppercase'>{label}</label>}
<div className='select-wrapper'>
<ReactSelect
styles={customStyles}
value={selected ? selected : ''}
options={options}
onChange={(val, i) => handleChange(val, i)}
isSearchable={true}
{...o}
/>
<input
id={`${id}_invalid}`}
name={`${id}_invalid}`}
value={selected ? selected : ''}
onChange={() => {}}
tabIndex={-1}
className={`form-control tp-input w-100 ${invalid ? '' : 'd-none'}`}
autoComplete='off'
//value={selected}
onFocus={() => {
setInvalid(false)
}}
style={{
position: 'absolute',
color: 'transparent',
backgroundColor: 'transparent',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 0,
}}
required={true}
/>
</div>
</Col>
)
}
look at this link:https://codesandbox.io/s/react-hook-form-controller-079xx?file=/src/index.js
you have to use Controller
<Controller
control={control}
rules={{ required: true }}
name="citySelect"
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
options={citiesOption}
value={city}
onChange={handleCity}
className="basic-single"
classNamePrefix="select"
isRtl
placeholder="استان مورد نظر"
as={Select}
/>
{errors.citySelect && <p>choose a city</p>}
React-Select style for Bootstrap 5 validation
class CustomSelect extends React.Component {
render() {
const { valid, invalid } = this.props;
let borderColor = '#ced4da';
let focusBorderColor = '#66afe9';
let focusBoxShadow = '0 0 0 .2rem rgba(0, 123, 255, 0.25)';
if (valid) {
borderColor = '#198754';
focusBorderColor = '#198754';
focusBoxShadow = '0 0 0 .2rem rgba(25, 135, 84, .25)';
} else if (invalid) {
borderColor = '#dc3545';
focusBorderColor = '#dc3545';
focusBoxShadow = '0 0 0 .2rem rgba(220, 53, 69, .25)';
}
const customStyles = {
valueContainer: (provided, state) => ({
...provided,
borderColor: state.selectProps.menuIsOpen ? focusBorderColor : borderColor,
boxShadow: state.selectProps.menuIsOpen ? focusBoxShadow : 'none',
}),
};
return (
<Select styles={customStyles} {...this.props} />
);
}
}

Resources