Select MenuItem doesn't show when JSX saved to state - reactjs

Working from the demo here: https://material-ui.com/demos/selects/ and I'm receiving some weird results. Specifically, when I select an item from the drop-down menu, the this.props.value updates fine... but the MenuItem does not show up.
If I put the <FormControl> tag directly in render, it works fine. If I put in an a variable, then setState with that and insert that into the render... it does NOT work.
Example:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import InputLabel from '#material-ui/core/InputLabel';
import MenuItem from '#material-ui/core/MenuItem';
import FormControl from '#material-ui/core/FormControl';
import Select from '#material-ui/core/Select';
const styles = theme => ({
root: {
display: 'flex',
flexWrap: 'wrap',
},
formControl: {
margin: theme.spacing.unit,
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing.unit * 2,
},
});
class Question extends React.Component {
state = {
age: '',
age2: '',
slct: ''
};
componentDidMount() {
const { classes } = this.props;
var mySelect =
<FormControl className={classes.formControl}>
<InputLabel htmlFor="age-simple">Age</InputLabel>
<Select
value={this.state.age}
onChange={this.handleChange}
inputProps={{
name: 'age',
id: 'age-simple',
}}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
this.setState({ slct: mySelect });
}
handleChange = event => {
this.setState({ [event.target.name]: event.target.value });
};
render() {
const { classes } = this.props;
return (
<div>
{this.state.slct}
<p>Value:</p>{this.state.age}
<FormControl className={classes.formControl}>
<InputLabel htmlFor="age-simple2">Age</InputLabel>
<Select
value={this.state.age2}
onChange={this.handleChange}
inputProps={{
name: 'age2',
id: 'age-simple2',
}}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
<p>Value:</p>{this.state.age2}
</div>
);
}
}
export default withStyles(styles)(Question);
You can see how the 'value' updates correctly for both based on which answer I select in the drop down... but visually, the MenuItem label never appears to show up for the one that's coming from state:
Help?
(fyi: these items are in a parents' <form>.)

it has to be a function or an Array for rendering. To make your code work, it should be:
state = {
slct: <FormControl>...</FormControl>
}
slctRenderer = () => this.state.slct;
render() {
return (
<div>
{this.slctRenderer()}
</div>
);
}

Using the information from #Jee Mok ... the idea that it might work as a function (even though other items don't seem to care) made me try the following changes to the initial code:
Added this function:
selectRenderer() {
const { classes } = this.props;
return (
<FormControl className={classes.formControl}>
<InputLabel htmlFor="age-simple">Age</InputLabel>
<Select
value={this.state.age}
onChange={this.handleChange}
inputProps={{
name: 'age',
id: 'age-simple',
}}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
);
};
And then in the component's render function, I call it:
render() {
return (
<div>
{this.selectRenderer()}
<p>Value:</p>{this.state.age}
</div>
);
}
This works. (and thus I'll mark it answered in two days)
I am curious, however, WHY this particular chunk of JSX needs to be brought in as a function whereas other chunks of JSX do not. (specifically, a material-ui text field works just fine being assigned to state as I did in the OP) I'd like to be able to build out this question once (preferably as part of the componentDidMount lifecycle) and then pass the built-out question somehow to the render function. State seems to be the way to do that, but I can't with Select/MenuItems for some reason.

Related

click on MUI's select in bootstrap dropdown makes it disappear

As the title said, I have a material UI's select in a bootstrap dropdown. When I click on the select, the dropdown closes, and the popover than appear is placed in a bad place.
Assuming that the popover should be created inside the dropdown for it not to happen, I tried to make it this way, using the anchorEl.
The console tells me "Popover.js:162 MUI: The anchorEl prop provided to the component is invalid.
The anchor element should be part of the document layout.
Make sure the element is present in the document or that it's not display none."
And it doesn't fix the issue. I'm quite lost, any idea? As a detail, I made a test using Button + Popover instead of the select, and it works.
Code:
import "./styles.css";
import React, { useRef, useState } from "react";
import Select from "#mui/material/Select";
import FormControl from "#mui/material/FormControl";
import MenuItem from "#mui/material/MenuItem";
function MySelect() {
const ref = useRef();
const [value, setValue] = useState("null");
const [anchor, setAnchor] = useState(null);
const handleChange = () => {};
const onOpen = (e) => {
setAnchor(e.currentTarget);
};
return (
<>
<div ref={ref}>
<FormControl fullWidth>
<Select
value={value}
onOpen={onOpen}
onChange={handleChange}
MenuProps={{ anchorEl: anchor, anchorReference: "anchorEl" }}
>
<MenuItem value="null">Hey</MenuItem>
<MenuItem value="hello">Hello</MenuItem>
<MenuItem value="hello2">Hello</MenuItem>
<MenuItem value="hello3">Hello</MenuItem>
<MenuItem value="hello4">Hello</MenuItem>
<MenuItem value="hello5">Hello</MenuItem>
<MenuItem value="hello6">Hello</MenuItem>
<MenuItem value="hello7">Hello</MenuItem>
<MenuItem value="hello8">Hello</MenuItem>
<MenuItem value="hello9">Hello</MenuItem>
<MenuItem value="hello10">Hello</MenuItem>
<MenuItem value="hello11">Hello</MenuItem>
<MenuItem value="hello12">Hello</MenuItem>
<MenuItem value="hello13">Hello</MenuItem>
<MenuItem value="hello14">Hello</MenuItem>
<MenuItem value="hello15">Hello</MenuItem>
<MenuItem value="hello16">Hello</MenuItem>
<MenuItem value="hello17">Hello</MenuItem>
<MenuItem value="hello18">Hello</MenuItem>
<MenuItem value="hello19">Hello</MenuItem>
<MenuItem value="hello20">Hello</MenuItem>
<MenuItem value="hello21">Hello</MenuItem>
</Select>
</FormControl>
</div>
</>
);
}
export default function App() {
return (
<div className="App" style={{ width: "400px" }}>
<div
id="my-dropdown"
className="dropdown"
style={{ marginLeft: "200px" }}
>
<a
id="my-btn"
href="#"
data-bs-toggle="dropdown"
data-bs-auto-close="outside"
aria-expanded="false"
>
Filter
</a>
<div
className="dropdown-menu dropdown-menu-sm-end"
aria-labelledby="my-btn"
>
<div>stuff to fill a bit</div>
<MySelect />
</div>
</div>
</div>
);
}
Link to try : https://codesandbox.io/s/lucid-babbage-5gfcqo?file=/src/App.js

How to make select label always appear at top in MUI?

I want to create a material ui selector that label always appear at top. I added placeholder to select component. Here is what I want to do.
When not focusing to select component or at the beginning, it looks like this.Label overlaps the place holder.
What I want to create select label always seem at the top and not overlaps the placeholder.
Here is the code at codesandbox
CodeSandbox
import * as React from "react";
import MenuItem from "#mui/material/MenuItem";
import FormControl from "#mui/material/FormControl";
import Select from "#mui/material/Select";
import InputLabel from "#mui/material/InputLabel";
const names = ["Oliver Hansen", "Van Henry", "April Tucker", "Ralph Hubbard"];
export default function MultipleSelectPlaceholder() {
const [personName, setPersonName] = React.useState(null);
const handleChange = (event) => {
setPersonName(event.target.value);
};
return (
<div>
<FormControl sx={{ m: 1, width: 300, mt: 3 }}>
<InputLabel id="demo-simple-select-label">Age</InputLabel>
<Select
labelId="demo-simple-select-label"
label="Age"
displayEmpty
value={personName}
onChange={handleChange}
renderValue={(selected) => {
if (selected === null) {
return <em>Please Choose Name</em>;
}
return selected;
}}
>
<MenuItem value={null}>
<em>Clear</em>
</MenuItem>
{names.map((name) => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
</Select>
</FormControl>
</div>
);
}
<script src="https://unpkg.com/#material-ui/core/umd/material-ui.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.1/umd/react-dom.production.min.js"></script>
https://codesandbox.io/s/multipleselectplaceholder-material-demo-forked-t06gm?file=/demo.js
Just add to Select:
notched={true} // Makes space between lines
and to InputLabel:
shrink={true} // Keeps label at top
For this purpose, you can simply pass shrink prop to the InputLabel component

How to use MUI Select with react-hook-form?

I've built a form in React using MUI and React Hook Form. I'm trying to create a custom TextField element that works as a Select Input. I would like it to be an uncontrolled component with a Ref prop. I've tried to pass the inputRef prop as the MUI and React Hook Form docs recommend but with no success.
<TextField
id="id"
name="name"
select
native="true"
className={classes.textField}
label="label"
margin="normal"
variant="outlined"
inputRef={register({ required: "Choose one option" })}
error={!!errors.name}
>
<MenuItem value="">Choose one option</MenuItem>
<MenuItem value="3">03</MenuItem>
<MenuItem value="6">06</MenuItem>
<MenuItem value="9">09</MenuItem>
<MenuItem value="12">12</MenuItem>
<MenuItem value="16">16</MenuItem>
<MenuItem value="18">18</MenuItem>
</TextField>
One thing that I've found is that if I use the native select with ref, it works just fine.
Besides, I tried to change the inputRef prop to a SelectProps one but it didn't work too.
Using Select component from Material-ui with react hook form need you to implement custom logic with a Controller https://react-hook-form.com/api#Controller
Here is a reusable component that will hopefully simplify the code to use that Select component in your app:
import FormControl from "#material-ui/core/FormControl";
import InputLabel from "#material-ui/core/InputLabel";
import Select from "#material-ui/core/Select";
import { Controller } from "react-hook-form";
const ReactHookFormSelect = ({
name,
label,
control,
defaultValue,
children,
...props
}) => {
const labelId = `${name}-label`;
return (
<FormControl {...props}>
<InputLabel id={labelId}>{label}</InputLabel>
<Controller
as={
<Select labelId={labelId} label={label}>
{children}
</Select>
}
name={name}
control={control}
defaultValue={defaultValue}
/>
</FormControl>
);
};
export default ReactHookFormSelect;
You can use it in your app like this:
<ReactHookFormSelect
id="numero_prestacao"
name="numero_prestacao"
className={classes.textField}
label="Em quantas parcelas?"
control={control}
defaultValue={numero_prestacao || ""}
variant="outlined"
margin="normal"
>
<MenuItem value="">Escolha uma opção</MenuItem>
<MenuItem value="3">03 parcelas</MenuItem>
<MenuItem value="6">06 parcelas</MenuItem>
<MenuItem value="9">09 parcelas</MenuItem>
<MenuItem value="12">12 parcelas</MenuItem>
<MenuItem value="16">16 parcelas</MenuItem>
<MenuItem value="18">18 parcelas</MenuItem>
</ReactHookFormSelect>
Here is your codeSandBox updated with this component for the selects in the Information form:
https://codesandbox.io/s/unit-multi-step-form-kgic4?file=/src/Register/Information.jsx:4406-5238
RHF v7 update
Below is a minimal code example of MUI Select in a RHF form:
const { formState, getValues, watch, register, handleSubmit } = useForm();
const { errors } = formState;
<TextField
select
fullWidth
label="Select"
defaultValue=''
inputProps={register('currency', {
required: 'Please enter currency',
})}
error={errors.currency}
helperText={errors.currency?.message}
>
{currencies.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
Accepted version is correct but outdated.
At least in the version that I'm using: "react-hook-form": "^7.30.0" you should use the render parameter.
Here is the "updated" version that perfectly works for me:
<FormControl>
<InputLabel id="level-label">Level</InputLabel>
<Controller
name="level"
id="level"
defaultValue={level}
control={control}
render={({ field }) => (
<Select labelId="level-label" {...field}>
<MenuItem value={0}>0</MenuItem>
<MenuItem value={1}>1</MenuItem>
</Select>
)}
/>
<FormHelperText error={true}>{errors.level?.message}</FormHelperText>
</FormControl>
The important here is to propagate the field properties down to the child element (Select in our case)
PS. I don't think you need a separate component for it, it is pretty straight forward.
[Updated]
Here is a full code of one of my dialog. As per request from Deshan.
import {
Box, Chip, FormControl, Input, Stack,
} from '#mui/material';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import debounce from '../#utils/debounce';
import useRawParams from '../#utils/useRawParams';
import { useBrandsSearchQuery } from '../data/products';
import { SearchRoute } from '../SBRoutes';
import LoadingDiv from './LoadingDiv';
import SBDialog from './SBDialog';
import { useSearchBarContext } from '../contexts/SearchBarContext';
const context = { suspense: false };
/**
* Show the modal dialog with the list of brands, and search box for it
* Eeach brand will be as a link, for the SEO purposes
*/
export default function AllBrandsDialog({ open, setOpen }) {
const [t] = useTranslation();
const [query, setQuery] = useState('');
const [brands, setBrands] = useState([]);
const params = useRawParams(true);
const paramsBrands = params.brands?.split(',') || [];
const { setFilterActive } = useSearchBarContext();
const variables = useMemo(() => (query.length ? {
filterText: query,
} : null), [query]);
const [{ data, fetching: loading }] = useBrandsSearchQuery({ variables, pause: Boolean(!variables), context });
const debounceSetQuery = useCallback(debounce(200, (text) => {
setQuery(text);
}));
useEffect(() => {
if (!data || !open) return;
setBrands(data.brands || []);
}, [data, open]);
return (
<SBDialog open={open} setOpen={setOpen} title={t('Search and select a brand')}>
<Stack direction="column" spacing={2}>
<FormControl>
<Input
id="tagSearch"
placeholder={t('Start typing to see the brands')}
onChange={(e) => debounceSetQuery(e.target.value)}
autoFocus={true}
/>
</FormControl>
<Box display="grid" width={220} height={300} overflow="auto" gap={1} position="relative">
{brands?.map((brand) => (
<Chip
component={Link}
key={brand.id}
disabled={paramsBrands.indexOf(brand.url) > -1}
to={SearchRoute.generatePath({
...params,
brands: [...paramsBrands, brand.url],
page: undefined,
})}
size="small"
label={brand.nicename}
variant="outlined"
onClick={() => {
setOpen(false);
setFilterActive(false);
}}
clickable={true}
/>
))}
{loading && <LoadingDiv modal={true} />}
</Box>
</Stack>
</SBDialog>
);
}
AllBrandsDialog.propTypes = {
open: PropTypes.bool.isRequired,
setOpen: PropTypes.func.isRequired,
};
Here my code that working, hope it can help, need to use setValue
<TextField
fullWidth
inputRef={register({
name: 'name',
})}
select
onChange={e => setValue('name', e.target.value, true)}
label={label}
defaultValue={defaultValue}
>
{options.map((option) => (
<MenuItem key={option.label} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
Here using native select, do not need setValue, but value alway string
<TextField
fullWidth
select
SelectProps={{
native: true,
inputProps: { ref: register, name: 'name' }
}}
label={label}
defaultValue={defaultValue}
>
{options.map((option) => (
<option key={option.label} value={option.value}>
{option.label}
</option>
))}
</TextField>
This is an example that uses Material-UI with React hook form. You need to add the validation in 'inputRef' prop of TextField. Also you need to add 'onChange' function to keep the state updated. 'shouldValidate' will trigger the validation.
<TextField
select
name='city'
inputRef={register({ required: true })}
onChange={e => setValue('city', e.target.value, { shouldValidate: true })}
label="City"
defaultValue="">
{cityList.map((option, index) => (
<MenuItem key={index} value={option}>
{option}
</MenuItem>
))}
</TextField>
{errors.city && <ErrorText>City is required</ErrorText>}
✔ I came across this same issue, and this is how i solved mine:
<Select ... onChange={e => register({ name: 'academicLevel', value: e.target.value })}/>
more info
When you using react-hook-form with material UI, you don´t need to use onChange and setState. Only use inputRef and all works!
Just need to pass the register to the Input Ref
<Select
variant="outlined"
name="reason"
inputRef={register({ required: true })}
>
just use mui-react-hook-form-plus
Here is an example:
import { HookSelect, useHookForm } from 'mui-react-hook-form-plus';
const defaultValues = {
person: {
firstName: 'Atif',
lastName: 'Aslam',
sex: '',
},
};
const App = () => {
const { registerState, handleSubmit } = useHookForm({
defaultValues,
});
const onSubmit = (_data: typeof defaultValues) => {
alert(jsonStringify(_data));
};
return (
<HookSelect
{...registerState('person.sex')}
label='SEX'
items={[
{ label: 'MALE', value: 'male' },
{ label: 'FEMALE', value: 'female' },
{ label: 'OTHERS', value: 'others' },
]}
/>
)
}
Repo: https://github.com/adiathasan/mui-react-hook-form-plus
Demo: https://mui-react-hook-form-plus.vercel.app/?path=/docs/

React multiple select form saving only one option to array

I'm trying to create a multiple select form. But when I submit, only the first option is saved into the array, not all selected options. I'm getting options from locations collection, and I'm submitting the form to the categories collection. What can be the problem?
const handleInputChange = (e) => {
setCategory({ ...category, [e.target.name]: e.target.value });
};
return (
<div>
<form onSubmit={handleSubmit}>
<div className='form-group'>
<select
name='location'
onChange={handleInputChange}
size={locations.length}
className='form-control'
multiple
>
{locations.map((location) => (
<option value={location.title}>
{location.title}
</option>
))}
</select>
</div>
<button>Add</button>
</form>
</div>
);
Please check this example:
import React from 'react';
import MenuItem from '#material-ui/core/MenuItem';
import FormControl from '#material-ui/core/FormControl';
import Select from '#material-ui/core/Select';
export default class DropDownMenuExample extends React.Component {
constructor(props) {
super(props);
this.state = {value: []};
}
handleChange(event) {
let value = event.target.value;
this.setState({value})
};
render() {
return (
<div>
<FormControl>
<Select
labelId="demo-controlled-open-select-label"
id="demo-controlled-open-select"
value={this.state.value}
multiple
onChange={this.handleChange.bind(this)}
>
<MenuItem value={1}>Blue</MenuItem>
<MenuItem value={2}>Red</MenuItem>
<MenuItem value={3}>Green</MenuItem>
<MenuItem value={4}>Black</MenuItem>
<MenuItem value={5}>Purple</MenuItem>
<MenuItem value={6}>Yellow</MenuItem>
<MenuItem value={7}>Maroon</MenuItem>
<MenuItem value={8}>Gray</MenuItem>
<MenuItem value={9}>Silver</MenuItem>
<MenuItem value={10}>Gold</MenuItem>
</Select>
</FormControl>
</div>
);
}
}

Material ui list of select item need to be selected

i have created selected box..using functional component...i am getting list of items from the back end, i am looping that list and showing in the front end....now i need to be selected item need to show in the box what i have selected...could any one help on this...
Please see my example code in this link https://codesandbox.io/s/gallant-water-fnqiv
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import InputLabel from "#material-ui/core/InputLabel";
import MenuItem from "#material-ui/core/MenuItem";
import FormControl from "#material-ui/core/FormControl";
import Select from "#material-ui/core/Select";
const useStyles = makeStyles(theme => ({
formControl: {
margin: theme.spacing(1),
minWidth: 120
},
selectEmpty: {
marginTop: theme.spacing(2)
}
}));
const lists = ["Ten", "twenty", "Thirty", "Fourty", "Fifity", "Sixty"];
export default function SimpleSelect() {
const classes = useStyles();
const [age, setAge] = React.useState("");
const inputLabel = React.useRef(null);
const [labelWidth, setLabelWidth] = React.useState(0);
React.useEffect(() => {
setLabelWidth(inputLabel.current.offsetWidth);
}, []);
const handleChange = event => {
setAge(event.target.value);
};
return (
<div>
<FormControl variant="outlined" className={classes.formControl}>
<InputLabel ref={inputLabel} id="demo-simple-select-outlined-label">
Age
</InputLabel>
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={age}
onChange={handleChange}
labelWidth={labelWidth}
>
<MenuItem value={""}>
{lists.map(item => {
<li>{item.list}</li>;
})}
</MenuItem>
</Select>
</FormControl>
</div>
);
}
There is no property in the array named, item.list , so you need to use item alone which itself gives the value you want.
Also make sure <MenuItem value={item}> is inside the lists.map() method..
Include return statement of the MenuItemis inside ofthe lists.map() method which throws error in your codesandbox link
And hence,
Change:
<MenuItem value={""}>
{lists.map(item => {
<li>{item.list}</li>;
})}
</MenuItem>
To:
{lists.map(item => {
return (
<MenuItem value={item}>
<li>{item}</li>
</MenuItem>
);
})}
Forked Codesandbox
you can try this-
<div>
<FormControl variant="outlined" className={classes.formControl}>
<InputLabel ref={inputLabel} id="demo-simple-select-outlined-label">
Age
</InputLabel>
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={age}
onChange={handleChange}
labelWidth={labelWidth}
>
{lists.map(item => (
<MenuItem value={item}>{item}</MenuItem>
))}
</Select>
</FormControl>
</div>
https://codesandbox.io/s/material-ui-select-m8lgt
Updated code sandbox link
Your MenuItem was wrong.
{lists.map(item => (
<MenuItem value={item}>{item}</MenuItem>
))}

Resources