Textfield position shifts up after making a selection MUI? - reactjs

I have this Autocomplete component, it renders a TextField with options to select from , problem is once I make a selection, it losses margin top, my bet is on the text, since there is no text there is no margin! I partially solved it by add in a empty space, which proves my point but still what is the right way to solve this, any idea how to stop this from happening and yet be able to pass an empty text without losing the margin.

The reason it doesn't work is because of the label, you set the label to an empty string whenever there is a selected value:
label={selected.item === "" ? "test" : ""}
Which affects the following rule:
label + .MuiInput-formControl {
margin-top: 16px;
}
If the label is empty, it will be removed from the DOM, so margin-top doesn't get applied anymore. A simple workaround is simply set the label to a non-empty string even if you don't want to display it:
label={selected.item === "" ? "test" : " "}

You can use placeholder props to the TextField component inside Autocomplete rather than conditional label props.
And then if you want to add margin, you can add some styling to the Autocomplete component like style={{marginTop:20}} or using MUI makeStyles API.
https://v4.mui.com/styles/api/#makestyles-styles-options-hook
...
<Autocomplete
id="combo-box-demo"
key={bool}
options={arr}
getOptionLabel={(option) => option}
onChange={handleChange}
style={{marginTop: 20}} // or using makeStyles
renderInput={(params) => (
<TextField
{...params}
placeholder="test" //label="test"
/>
)}
/>
...

Related

MUI & Formik: Validation doesn't trigger for useField

Codesandbox with minimal working example: https://codesandbox.io/s/mystifying-lake-m0oxc?file=/src/DemoForm.tsx
Essentially, I have a AutoComplete dropdown which is tied into a Formik form with the useField hook. This sets the value correctly on any change, but the validation doesn't seem to trigger when I expect it to.
Validation runs successfully and removes the error if:
Another field is changed
I click on the "background" after selecting a value in the dropdown
What I expected and wanted was that the validation should run immediately when a value is selected.
To reproduce:
Click the category dropdown
Select a value, which closes the dropdown
Confirm that the error is still indicated (on the field and in the printout)
Click elsewhere on the screen. This should trigger validation and clear the error.
Any suggestions?
Edit:
I've tested an equivalent implementation with react-select as the dropdown and had the same issue, so I don't think it's directly tied to MUI.
I just reproduced based on the working example what you provided and realized that you set helpers.setTouched manually.
Just don't overuse the setTouched and also you need to handle if you remove the selected item from the autocomplete.
<Autocomplete
disablePortal
id="category-selector"
options={options}
onBlur={() => helpers.setTouched(true, true)}
isOptionEqualToValue={(option, value) => option.id === value.id}
onOpen={() => helpers.setTouched(true, true)}
onChange={(_, option) => {
if (option) {
helpers.setValue(option.id);
} else {
helpers.setValue(0);
}
}}
renderInput={(params) => (
<TextField
{...params}
label="Category"
error={meta.touched && Boolean(meta.error)}
/>
)}
/>

How to I keep a Material-ui Select open when I click on only one of the items in it

I have been writing a custom Material-UI Select dropdown which has an optional text field at the top to allow the user to search / filter items in the Select if there were many entries.
I am struggling with how to keep the Select open when I click on the text field (rendered as an InputBase) and just have the normal behavior (of closing the Select when a regular MenuItem is selected.
CodeSandbox here : https://codesandbox.io/s/inspiring-newton-9qsyf
const searchField: TextField = props.searchable ? (
<InputBase
className={styles.searchBar}
onClick={(event: Event) => {
event.stopPropagation();
event.preventDefault();
}}
endAdornment={
<InputAdornment position="end">
<Search />
</InputAdornment>
}
/>
) : null;
return (
<FormControl>
<Select
className={styles.root}
input={<InputBase onClick={(): void => setIconOpen(!iconOpen)} />}
onBlur={(): void => setIconOpen(false)}
IconComponent={iconOpen ? ExpandMore : ExpandLess}
{...props}
>
{searchField}
{dropdownElements.map(
(currEntry: string): HTMLOptionElement => (
<MenuItem key={currEntry} value={currEntry}>
{currEntry}
</MenuItem>
)
)}
</Select>
</FormControl>
);
As you can see above I've tried using stopPropagation and preventDefault but to no avail.
check out this codesandbox link: https://codesandbox.io/s/busy-paper-9pdnu
You can use open prop of Select API
I was able to make a controlled open select by providing open prop as a react state variable and implementing correct event handlers. To make it controlled you must provide onOpen and onClose props of the Select and make sure the open prop stays true when the custom textfield is clicked.
One more important thing I had to do was override the default keyDown behavior of the Select component. If you open up a Select and start typing into it, it shifts focus to the select option that matches what you are typing. For example, if you Select had an option with the text Foobar and if you start typing Food and water, it would cause focus to shift from your custom text input onto the Foobar option. This behavior is overridden in the onKeyDown handler of the custom textfield
Working sandbox here
Edit: even though this worked in the codepen, I had to add onChange={handleOpen} to the Select as well to get it working on a real browser with React and Next.
You can still use stopPropagation to make it work
// open state
const [isSelectorOpen, setisSelectorOpen] = useState(false)
// handle change
const handleChange = event => {
const { value } = event.target
event.stopPropagation()
// set your value
}
// selector
<Select
multiple
open={isSelectorOpen}
onChange={handleChange}
input={(
<Input
onClick={() => setisSelectorOpen(!isSelectorOpen)}
/>
)}
// other attribute
>
<MenuItem>a</MenuItem>
<MenuItem>b</MenuItem>
<MenuItem>c</MenuItem>
</Select>
In my case, none of the above worked, but this did the trick to stop closing the Select:
<MenuItem
onClickCapture={(e) => {
e.stopPropagation();
}}>
You can also change onMouseEnter to change some default styling that comes with using MenuItem (like pointer cursor when mouse enters its layout)
onMouseEnter={(e) => {
e.target.style.backgroundColor = "#ffffff";
e.target.style.cursor = "default";
}}
In my case i also needed to remove the grey clicking effect that MenuItem makes on click, which is a new object generated in MuiTouchRipple-root, so changing display to none did the trick.
sx={{
"& .MuiTouchRipple-root": {
display: "none",
},
}}

Material-UI checkbox backrground color

I am using material ui checkbox, i wanted it to have its own background color over my div which has colored background. I have set the root to have a backgroundColor as white but the svgicon is a round shape which is not the look i intend to have. Can i shape the checkbox ?
Already have tried many things but not able to figure out how to change the icon
const styles = {
root : {
padding : '0px',
display : 'inline-block',
backgroundColor : 'white'
},
formLabelRoot : {
margin : '0'
}
}
.
.
.
render () {
const { classes } = this.props
return(
<div style={customStyles.divStyle}>
<div style={customStyles.div1}>
<FormControlLabel
classes={{root: classes.formLabelRoot}}
control={
<Checkbox
classes={{root : classes.root }}
color='primary'
/>
}
label={''}
/>
</div>
The background white is making a spherical rounded checkbox apparent
Image of what is happening now
You can override the icons used for the checked and unchecked states. The props you're looking for are icon for the unchecked state and checkedIcon for the checked state. Both of which take a component to be used as the icon.
Documentation here
You might have to make a compromise here if you don't want to go making your own icons.
Something like this is possible without changing the icons. If you're looking for a solid white box with a grey outline as the unchecked box, you might have to provide that svg yourself because I don't see anything like it in #material-ui/icons.
If this IS something you're ok with then target the svg, and give it a fill: "white".

React Material UI RadioGroup not working with FormControlLabel returned by component

I have an issue working with the material UI RadioGroup in combination with FormControlLabels returned by a component.
Currently I try this:
<FormControl component="fieldset">
<RadioGroup
row
name="genreSelection"
className="genre-wrapper"
value={this.state.selection}
onChange={this.handleRadioSelection}
>
{
this.state.genres.map(genre => (
<GenreItem key={genre} label={genre} radioButton/>
))
}
</RadioGroup>
</FormControl>
And the render function of GenreItem looks like this:
if (this.props.radioButton) {
return (
<FormControlLabel
value={this.props.label}
label={this.props.label}
control={
<Radio/>
}
className="genre-item"
/>
);
}
// Another return here
My problem is that the FormControlLabel isn't working correctly as "handleRadioSelection" isn't triggered on any selection.
By now I found out that the input element generated from the Radio element doesn't have name="genreSelection" as attribute which means it doesn't belong to the RadioGroup defined above.
If I put the FormControlLabel out of GenreItem and directly into the this.state.genres.map function everything is working just fine.
Am I doing something wrong or is this just a limitation of material UI?
If you look at the code of RadioGroup you'll find that it clones the children that you provide to it and adds several properties. You need to carry those properties down into FormControlLabel.
You can accomplish that by changing GenreItem to render the FormControlLabel as follows:
// assign radioButton separately so it isn't part of labelProps
const { radioButton, ...labelProps } = this.props;
if (radioButton) {
<FormControlLabel {...labelProps }
label={this.props.value}
control={
<Radio/>
}
className="genre-item"
/>
Adding {...labelProps} will carry forward the props added by RadioGroup.
Also if you want the label and value to be the same, it is important to pass value explicitly to GenreItem rather than label since RadioGroup uses the value prop to help determine the checked property.
Here's a working example:

Adding ErrorText to material-ui Textfield moves other elements

I have a signup form in a React app. I am using material-ui TextField and use the errorText property to add a message if there is an error in the input.
errorText={this.state.messages.emailMessage}
The state.messages.emailMessage is initially set to null and so TextField does not have the extra space for the message when the input is first rendered.
When the message is added it moves the other elements.
How can I allow space for the new node if it is needed so that the other elements are not moved? I tried setting the initial state of the message to ' ' but this colours the input red for error and doesn't work anyway!
You could just use the errorStyle property setting an absolute position..
That's how I fix those problems in my projects.
In the end I passed a style parameter to the material-ul component that set the errorText to display: table. This then stopped it from affecting the other elements when it was added.
To where should this style added?
It needs to be added for the HelperText styles. Here is an example:
const helperStyles = makeStyles(theme => ({
root: {
position: 'absolute',
bottom: '1em',
},
}))
const helperClasses = helperStyles()
<FormHelperText classes={helperClasses}>
{helperText}
</FormHelperText>
You can make them a fixed height by making the helperText equal to an empty space when there is no message to show.
<TextField helperText={error ? error.message : ' '} />
Like #leonormes 's post suggests, adding the errorStyle prop to the material ui component and setting the display property to "table" solved this issue.
The material UI components no longer moves or becomes unaligned when showing an error.
Here's what the component ended up looking like:
<TextField
floatingLabelText="Name"
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}
errorText={this.props.validationErrors.get('name')}
errorStyle={{ display: "table" }}
/>
You can target the MuiFormHelperText-root class . Below example is when you are applying style inside MUI makeStyles , but you can do the same thing with external CSS file .
'& .MuiFormHelperText-root' : {
position : 'absolute',
bottom : '-1rem'
}
For those who need an updated answer (errorText isn't a thing anymore as far as I could tell), then hopefully this will work:
<Box style={{ minHeight: "100px" }} >
<TextField
{...props}
/>
</Box>
It allows the error text message to be rendered inside the flexbox without affecting the other components, so it shouldn't disturb the things around it.
CodeSandbox
You can do something like this
const useStyles = makeStyles({
helperText: {
'& .MuiFormHelperText-root': {
height: '0',
marginTop: '0'
}
}
});
And then add this class text field control
className={classes.helperText}
Just setting position to absolute didn't work at all. This enables error text to display on input field. So for perfect fix we also have to set bottom with some value to make it fixed. Example below.
errorStyle:{
position: 'absolute',
bottom: '-0.9rem'
}
As mentioned in other answer setting display to table worked as well.
So both the styles works

Resources