we can see the example here at https://material-ui.com/components/autocomplete/
I wanted to set option label and value different.
Like here is example used
const defaultProps = {
options: top5Films,
getOptionLabel: (option) => option.title,
};
<Autocomplete
{...defaultProps}
id="auto-complete"
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
autoComplete
includeInputInList
renderInput={(params) => <TextField {...params} label="clearOnEscape" margin="normal"/>}
/>
const top5Films= [
{ title: 'The Shawshank Redemption', year: 1994 },
{ title: 'The Godfather', year: 1972 },
{ title: 'The Godfather: Part II', year: 1974 },
{ title: 'The Dark Knight', year: 2008 },
{ title: '12 Angry Men', year: 1957 }
]
But I have data like:
const top5Films= [
{ id: 1, title: 'The Shawshank Redemption', year: 1994 },
{ id: 2, title: 'The Godfather', year: 1972 },
{ id: 3, title: 'The Godfather: Part II', year: 1974 },
{ id: 4, title: 'The Dark Knight', year: 2008 },
{ id: 5, title: '12 Angry Men', year: 1957 }
]
I want to set id as value and show title as label.
Looks like the object is assigned to the value.
So setting id to value crashed the options.
I used the id from the object in following way for further operation.
/* eslint-disable no-use-before-define */
import React from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
export default function Playground() {
const [value, setValue] = React.useState(null);
const [id, setId] = React.useState(null);
const [title, setTitle] = React.useState(null);
return (
<div>
<div>{`value: ${value}`}</div>
<div>{`id: ${id}`}</div>
<div>{`title: ${title}`}</div>
<br />
<div style={{ width: 300 }}>
<Autocomplete
options={top5Films}
getOptionLabel={option => option.title}
id="movies"
value={value}
onChange={(event, newValue) => {
console.log(newValue);
if (newValue) {
setValue(newValue);
setId(newValue.id);
setTitle(newValue.title);
}
}}
renderInput={params => (
<TextField {...params} label="Movies" margin="normal" />
)}
/>
</div>
</div>
);
}
// Top 5 films as rated by IMDb users. http://www.imdb.com/chart/top
const top5Films = [
{ id: 1, title: "The Shawshank Redemption", year: 1994 },
{ id: 2, title: "The Godfather", year: 1972 },
{ id: 3, title: "The Godfather: Part II", year: 1974 },
{ id: 4, title: "The Dark Knight", year: 2008 },
{ id: 5, title: "12 Angry Men", year: 1957 }
];
This works for now but best answer is always welcome.
I couldn't fully understand your issue but I guess you want to have different display option for getOptionLabel and different display in the dropdown list. If that's the case, you can simply use renderOption provided by the Material UI. Check out an example here : https://codesandbox.io/s/94xlp?file=/demo.js
import React from "react";
import { TextField } from "#material-ui/core";
import { Autocomplete } from "#material-ui/lab";
return (
<Autocomplete
freeSolo
name=""
options={top5Films }
getOptionLabel={(option) => option.title} //Displays title only in input field
renderOption={(option) => (
<React.Fragment>
{option.title + "," + " " + option.year} //Displays title + year in dropdown list
</React.Fragment>
)}
renderInput={params => (
<Field
component={FormikTextField}
{...params}
label=""
name=""
/>
)}
/>
);
}
Try this, set a json value and use the name for inputValue, the input value not change why not calling onChange function
<Autocomplete
options={machineList}
inputValue={formValues.machine.name || ""} // from formik context in my case
onChange={(e, value) => {
setValues(
"machine",
value && value.id ? { id: value.id, name: value.name } : "" //use json value
);
}}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<InputField
{...params}
type="text"
name={machine.name}
label={machine.label}
fullWidth
/>
)}
/>;
okk you can try in this way -
const options = [
{
id: 212,
label: 'First Title'
},
{
id: 321,
label: 'Second Title'
},
{
id: 543,
label: 'Third Title'
}
]
<Autocomplete
placeholder="Search…"
options={options}
isOptionEqualToValue={(option, value) => option.id === value.id}
renderInput={(params) => <TextField {...params} label={'label'} />}
renderOption={(props, option) => {
return (
<li {...props}>
{option.label}
</li>
)
}}
onChange={(_event, value) => {
setValue(value.id)
}}
/>
Related
I get from the API a json, with a list of each staff member:
const MOCK_STAFF = [{
id: 1,
name: "Jhon Doe",
department: "HR"
}, {
id: 2,
name: "Jane Doe",
department: "Research"
}, etc
Then they get mapped in a datalist <option>, inside a Form.Control component:
<Form.Group className="mb-3">
<Form.Label>Name</Form.Label>
<Form.Control
name='staffName'
value={0}
list="namesList"
onChange={(e) => onChangeHandler(e)}/>
<Form.Label>Department</Form.Label>
<Form.Control disabled
name=department
value={}
/>
<datalist id="namesList">
{MOCK_DATA.map( (data) => (
<option key={data.id} value={data.department}>{data.name}</option>
))}
</datalist>
</Form.Group>
sandbox link: https://codesandbox.io/s/modal-t59e7z?file=/src/App.tsx
I would like the onChange={(e) => onChangeHandler(e)} to get the data-value of <option key={data.id} on form submit, and to make the department Form.Control to reference the value of <option value={data.department} in the datalist. The 'key' id must not show to the user, it is used as a primary key on the database.
I have tried:
function onChangeHandler(e:React.SyntheticEvent) {
console.log(e.target.key);
}
but "property key does not exist on event.target". Nor I can use document.getElementById(); with react. How can I get the values 'key', 'value' and/or 'default-value` from a Form.Control with a datalist?
Thank you
I could not achieve this with data-list, but did so with react-select:
type StaffOption = {
label: string, value: number
}
const MOCK_DATA= [{
id: 1,
name: "Jhon Doe",
department: "HR"
}, {
id: 2,
name: "Jane Doe",
department: "Research"
}, {
id: 3,
name: "Elizabeth meyer",
department: "Operations"
}]
type NameOption = {value: number, label: string, department: string}
type NameOptions = Array<NameOption>
function AddNewRowModal(props:AddNewRowProps) {
const [selectedStaffID, setSelectedStaffID] = useState(0);
function onChangeHandler(option: OnChangeValue<StaffOption, false>,
actionMeta: ActionMeta<StaffOption>) {
console.log(option); //this returns all 3 values defined on type StaffOption
if (option?.value !== undefined) {
setSelectedStaffID(option.value!);
}
}
function BuildOptions (data:any[]) {
var options:NameOptions = []
data.forEach(element => {
options.push({
value: element.id!,
label: (element.name),
department: element.department});
});
return options;
var nameOptions = BuildOptions(MOCK_DATA);
return (
<Modal
show={props.showModal}
backdrop="static"
keyboard={false}
onHide={() => props.closeModal()} >
<Modal.Header closeButton>
<Modal.Title>Add new Entry</Modal.Title>
</Modal.Header>
<Modal.Body>
<Select
options={nameOptions}
onChange={onChangeHandler} />
</Modal.Body>
<ModalFooter>
<Button variant='primary'>Create Entry</Button>
<Button variant='danger' onClick={() => props.closeModal()}>Cancel</Button>
</ModalFooter>
</Modal>
);
}
And the codesandbox
I am pretty new to JavaScript and was hoping someone could help me out with this one.
I have this page that shows all the scheduled exams and when you press on "Learn more" a modal opens up that should let you modify information about the exam itself. At the moment it shows the equipment that is selected when creating the exam plus the rest of the equipment available and you should be able to select/deselect in order to change if needed. The problem is that a different modal opens up for each exam to show the corresponding data only. All the exam information I've got shown through mapping to get to the inside arrays of the "exams" nested array, so I do not know how to initialize a const before the render when I need the modal to be open to get that specific exams' info. At the moment I am mapping the values of the selected equipment which does not let me change the selection like I should.
https://codesandbox.io/s/81xer5
import "./styles.css";
import React, { useState, useEffect } from "react";
import Box from "#mui/material/Box";
import Card from "#mui/material/Card";
import CardActions from "#mui/material/CardActions";
import CardContent from "#mui/material/CardContent";
import Button from "#mui/material/Button";
import Typography from "#mui/material/Typography";
import Modal from "#mui/material/Modal";
import Chip from "#mui/material/Chip";
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";
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250
}
}
};
const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "background.paper",
boxShadow: 24,
p: 4,
borderRadius: 1
};
export default function App() {
const [exams, setExams] = useState([
{
id: "18897a8c-bd5b-4fc0-86d1-74ee509d46ee",
name: "New Test",
date: null,
time: null,
date2: "2022-06-20",
time2: "15:30",
students: [
{
id: "749ce920-2462-457a-8af3-26ff9c00dda5",
username: "student1",
email: "student1#gmail.com",
firstName: "Student",
lastName: "Studentov",
roleName: "STUDENT"
},
{
id: "90289548-19bb-480b-81e3-c36340debbc7",
username: "student2",
email: "student2#gmail.com",
firstName: "Student",
lastName: "Studentov",
roleName: "STUDENT"
},
{
id: "dfe50fe5-ef9d-480e-aa6c-2f5c81bb22da",
username: "student3",
email: "student3#gmail.com",
firstName: "Student",
lastName: "Studentov",
roleName: "STUDENT"
}
],
staff: [
{
id: "a3b53ed0-63fc-4f77-a8dc-74915d6aefea",
username: "staff",
email: "staff#gmail.com",
firstName: "Staff",
lastName: "Staffov",
roleName: "STAFF"
}
],
rooms: [
{
id: "a49f18cb-4fe8-4a2c-a665-4361c5401f31",
number: 100,
nrOfSeats: 20
},
{
id: "5c46e888-fce4-4c1b-a8ec-e04d32a5cf6c",
number: 400,
nrOfSeats: 10
}
],
equipment: [
{
id: "08506d1b-30ce-43d2-a0b8-74f87082e356",
name: "Crane",
availability: true
}
]
},
{
id: "65b7ecd2-ba30-4369-9f13-9186dc5cc73c",
name: "Crane Exam",
date: null,
time: null,
date2: null,
time2: null,
students: [
{
id: "749ce920-2462-457a-8af3-26ff9c00dda5",
username: "student1",
email: "student1#gmail.com",
firstName: "Student",
lastName: "Studentov",
roleName: "STUDENT"
},
{
id: "90289548-19bb-480b-81e3-c36340debbc7",
username: "student2",
email: "student2#gmail.com",
firstName: "Student",
lastName: "Studentov",
roleName: "STUDENT"
},
{
id: "dfe50fe5-ef9d-480e-aa6c-2f5c81bb22da",
username: "student3",
email: "student3#gmail.com",
firstName: "Student",
lastName: "Studentov",
roleName: "STUDENT"
}
],
staff: [
{
id: "a3b53ed0-63fc-4f77-a8dc-74915d6aefea",
username: "staff",
email: "staff#gmail.com",
firstName: "Staff",
lastName: "Staffov",
roleName: "STAFF"
}
],
rooms: [
{
id: "a49f18cb-4fe8-4a2c-a665-4361c5401f31",
number: 100,
nrOfSeats: 20
},
{
id: "5c46e888-fce4-4c1b-a8ec-e04d32a5cf6c",
number: 400,
nrOfSeats: 10
}
],
equipment: [
{
id: "08506d1b-30ce-43d2-a0b8-74f87082e356",
name: "Crane",
availability: true
},
{
id: "be1da3c9-7192-459f-bdba-767e005eaac9",
name: "Killer Robot",
availability: true
}
]
}
]);
const [equipment, setEquipment] = useState([
{
id: "08506d1b-30ce-43d2-a0b8-74f87082e356",
name: "Crane",
availability: true
},
{
id: "7a1716c7-3398-4e3d-9523-7ba4a102a79b",
name: "Lift",
availability: true
},
{
id: "be1da3c9-7192-459f-bdba-767e005eaac9",
name: "Killer Robot",
availability: true
}
]);
const initialShowState = Object.fromEntries(
exams.map((data) => [data.id, false])
);
const [show, setShow] = React.useState(initialShowState);
const toggleShow = (id) =>
setShow((prev) => {
return { ...prev, [id]: !prev[id] };
});
console.log({ show });
const [value, setValue] = React.useState([]); //this is what the select chip uses by default
const handleChange = (e) => {
const {
target: { value }
} = e;
console.log(value);
setValue(
// On autofill we get a the stringified value.
typeof value === "string" ? value.split(",") : value
);
};
return (
<div className="App">
{exams.map((data, key) => {
return (
<div key={key} style={{ width: "300px", display: "inline-block" }}>
<Box
sx={{
minWidth: 300,
maxWidth: 300,
display: "inline-block",
paddingTop: "10px",
paddingLeft: "10px"
}}
>
<Card variant="outlined">
<React.Fragment>
<CardContent>
<Typography variant="h5" component="div">
{data.name}
</Typography>
</CardContent>
<CardActions>
<Button size="small" onClick={() => toggleShow(data.id)}>
Learn More
</Button>
</CardActions>
</React.Fragment>
</Card>
</Box>
<Modal open={show[data.id]} onClose={() => toggleShow(data.id)}>
<Box sx={style}>
<Typography
component={"span"}
id="transition-modal-description"
sx={{ mt: 2 }}
>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="demo-multiple-chip-label">Chip</InputLabel>
<Select
multiple
value={data.equipment.map((sub) => sub.id)}
// value={value}
onChange={handleChange}
input={
<OutlinedInput id="select-multiple-chip" label="Chip" />
}
renderValue={(selected) => {
return (
<Box
sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}
>
{selected.map((value) => {
const option = equipment.find(
(o) => o.id === value
);
return <Chip key={value} label={option.name} />;
})}
</Box>
);
}}
MenuProps={MenuProps}
>
{equipment.map((option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</Select>
</FormControl>
</Typography>
</Box>
</Modal>
</div>
);
})}
</div>
);
}
Continuing my comment above, you're adding a <Modal> inside the map function, this will mount a <Modal> element for each exam, which is bad for performance and more difficult to implement.
What you want to do is to have only one modal, and upon clicking "Learn More" you save the active exam in a state, the modal uses this state to show the correct data. You also want to split the logic between the exam and the modal to make it more easy to implement.
Here is a sample code, I have moved the arrays outside the component to make the code more clear:
const EXAMS = [...];
const EQUIPMENTS = [...];
export default function App() {
const [exams, setExams] = useState(EXAMS);
const [equipment, setEquipment] = useState(EQUIPMENTS);
const [modalExam, setModalExam] = useState(null);
return (
<div className="App">
{exams.map((data, key) => {
return (
<div key={key} style={{ width: "300px", display: "inline-block" }}>
<Box
sx={{
minWidth: 300,
maxWidth: 300,
display: "inline-block",
paddingTop: "10px",
paddingLeft: "10px",
}}
>
<Card variant="outlined">
<React.Fragment>
<CardContent>
<Typography variant="h5" component="div">
{data.name}
</Typography>
</CardContent>
<CardActions>
<Button size="small" onClick={() => setModalExam(data)}>
Learn More
</Button>
</CardActions>
</React.Fragment>
</Card>
</Box>
</div>
);
})}
<ModalExam
equipment={equipment}
exam={modalExam}
onClose={() => setModalExam(null)}
/>
</div>
);
}
function ModalExam({ exam, equipment, onClose }) {
const [chipValue, setChipValue] = useState([]);
useEffect(() => {
if (exam) {
setChipValue(exam.equipment.map((sub) => sub.id));
}
}, [exam]);
const handleChange = (e) => {
const {
target: { value },
} = e;
console.log(value);
setChipValue(typeof value === "string" ? value.split(",") : value);
};
return (
<Modal open={exam !== null} onClose={onClose}>
{exam && (
<Box sx={style}>
<Typography
component={"span"}
id="transition-modal-description"
sx={{ mt: 2 }}
>
<p>{exam.name}</p>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="demo-multiple-chip-label">Chip</InputLabel>
<Select
multiple
value={chipValue}
// value={value}
onChange={handleChange}
input={<OutlinedInput id="select-multiple-chip" label="Chip" />}
renderValue={(selected) => {
return (
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
{selected.map((value) => {
const option = equipment.find((o) => o.id === value);
return <Chip key={value} label={option.name} />;
})}
</Box>
);
}}
MenuProps={MenuProps}
>
{equipment.map((option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</Select>
</FormControl>
</Typography>
</Box>
)}
</Modal>
);
}
See how much simple it gets when you split the logic. Here is the sandbox: https://codesandbox.io/s/hedk9g
As some background
With Material UI Multiple Select, you can truncate the value shown after selection, rather than going to another line (by setting the renderValue to .join the selected options, which gives the functionality of "option A, option B, ..."). The important piece here is that it adds the "..." when the selected options are too long to fit on one line, rather than expanding to the next line.
For example, the following works with Multiple Select:
// Truncated value (I DO want this, for `Autocomplete`)
<Select
labelId="demo-mutiple-name-label"
id="demo-mutiple-name"
multiple
value={personName}
onChange={handleChange}
input={<Input />}
renderValue={selected => selected.join(", ")}
MenuProps={MenuProps}
>
{names.map(name => (
<MenuItem
key={name}
value={name}
style={getStyles(name, personName, theme)}
>
{name}
</MenuItem>
))}
</Select>
// – VERUS –
// chips that wrap to multiple lines (DON'T want this)
<Select
labelId="demo-mutiple-chip-label"
id="demo-mutiple-chip"
multiple
value={personName}
onChange={handleChange}
input={<Input id="select-multiple-chip" />}
renderValue={selected => (
<div className={classes.chips}>
{selected.map(value => (
<Chip key={value} label={value} className={classes.chip} />
))}
</div>
)}
MenuProps={MenuProps}
>
{names.map(name => (
<MenuItem
key={name}
value={name}
style={getStyles(name, personName, theme)}
>
{name}
</MenuItem>
))}
</Select>
Demo of Multiple Selects
I'm trying to replicate the functionality above with the Material UI Autocomplete
but there doesn't seem to be an obvious way to do so.
I've tried several approaches:
<Autocomplete
multiple
id="tags-standard"
options={top100Films}
getOptionLabel={option => option.title}
defaultValue={[top100Films[13]]}
renderTags={selected => {
console.log("selected = ", selected);
let renderTagsValue = selected
.map(function(elem) {
return elem.title;
})
.join(", ");
return (
<Typography noWrap={true} color="textPrimary">
{renderTagsValue}
</Typography>
);
}}
renderInput={params => (
<TextField
{...params}
variant="standard"
label="Multiple values"
placeholder="Favorites"
/>
)}
/>
renderTags – Since renderValue is not an option for Autocomplete's, I added a .join to the renderTags, but that only creates a long string that continues to wrap to the next line
disableListWrap – I hoped this would prevent anything from wrapping to the next line, but it still wraps to the next line
limitTags – This doesn't work since tags can be variable lengths. 1, 2, or 3 may fit on a line depending on which tag is selected
renderTags AND Typography – same as #1, plus returning a Typography element with noWrap set to true (this is close but still not right
My closest attempt is #4, but it's still not right. It truncates, but still wraps the placeholder text to the next line, making the textbox's height expand vertically, rather than remaining fixed (like the demo with Multiple Select).
Demo of closest attempt
Does anyone know how to replicate Material UI Multiple Select's truncated renderValue with Material UI Autocomplete?
I get reasonable behavior by just adding style={{ maxWidth: 360 }} to the Typography in your sandbox in order to leave room for the placeholder and some space to type.
Here's the full code:
/* eslint-disable no-use-before-define */
import React from "react";
import Chip from "#material-ui/core/Chip";
import Autocomplete from "#material-ui/lab/Autocomplete";
import { makeStyles } from "#material-ui/core/styles";
import TextField from "#material-ui/core/TextField";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles(theme => ({
root: {
width: 500,
"& > * + *": {
marginTop: theme.spacing(3)
}
}
}));
export default function Tags() {
const classes = useStyles();
return (
<div className={classes.root}>
<Autocomplete
multiple
disableListWrap={true}
disableCloseOnSelect
id="tags-standard"
options={top100Films}
getOptionLabel={option => option.title}
defaultValue={[top100Films[13]]}
renderTags={selected => {
console.log("selected = ", selected);
let renderTagsValue = selected
.map(function(elem) {
return elem.title;
})
.join(", ");
return (
<Typography
style={{ maxWidth: 360 }}
noWrap={true}
color="textPrimary"
>
{renderTagsValue}
</Typography>
);
}}
renderInput={params => (
<TextField
{...params}
variant="standard"
label="Multiple values"
placeholder="Favorites"
/>
)}
/>
</div>
);
}
// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top
const top100Films = [
{ title: "The Shawshank Redemption", year: 1994 },
{ title: "The Godfather", year: 1972 },
{ title: "The Godfather: Part II", year: 1974 },
{ title: "The Dark Knight", year: 2008 },
{ title: "12 Angry Men", year: 1957 },
{ title: "Schindler's List", year: 1993 },
{ title: "Pulp Fiction", year: 1994 },
{ title: "The Lord of the Rings: The Return of the King", year: 2003 },
{ title: "The Good, the Bad and the Ugly", year: 1966 },
{ title: "Fight Club", year: 1999 },
{ title: "The Lord of the Rings: The Fellowship of the Ring", year: 2001 },
{ title: "Star Wars: Episode V - The Empire Strikes Back", year: 1980 },
{ title: "Forrest Gump", year: 1994 },
{ title: "Inception", year: 2010 },
{ title: "The Lord of the Rings: The Two Towers", year: 2002 },
{ title: "One Flew Over the Cuckoo's Nest", year: 1975 },
{ title: "Goodfellas", year: 1990 },
{ title: "The Matrix", year: 1999 },
{ title: "Seven Samurai", year: 1954 },
{ title: "Star Wars: Episode IV - A New Hope", year: 1977 },
{ title: "City of God", year: 2002 },
{ title: "Se7en", year: 1995 },
{ title: "The Silence of the Lambs", year: 1991 },
{ title: "It's a Wonderful Life", year: 1946 },
{ title: "Life Is Beautiful", year: 1997 },
{ title: "The Usual Suspects", year: 1995 },
{ title: "Léon: The Professional", year: 1994 },
{ title: "Spirited Away", year: 2001 },
{ title: "Saving Private Ryan", year: 1998 },
{ title: "Once Upon a Time in the West", year: 1968 },
{ title: "American History X", year: 1998 },
{ title: "Interstellar", year: 2014 },
{ title: "Casablanca", year: 1942 },
{ title: "City Lights", year: 1931 },
{ title: "Psycho", year: 1960 },
{ title: "The Green Mile", year: 1999 },
{ title: "The Intouchables", year: 2011 },
{ title: "Modern Times", year: 1936 },
{ title: "Raiders of the Lost Ark", year: 1981 },
{ title: "Rear Window", year: 1954 },
{ title: "The Pianist", year: 2002 },
{ title: "The Departed", year: 2006 },
{ title: "Terminator 2: Judgment Day", year: 1991 },
{ title: "Back to the Future", year: 1985 },
{ title: "Whiplash", year: 2014 },
{ title: "Gladiator", year: 2000 },
{ title: "Memento", year: 2000 },
{ title: "The Prestige", year: 2006 },
{ title: "The Lion King", year: 1994 },
{ title: "Apocalypse Now", year: 1979 },
{ title: "Alien", year: 1979 },
{ title: "Sunset Boulevard", year: 1950 },
{
title:
"Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb",
year: 1964
},
{ title: "The Great Dictator", year: 1940 },
{ title: "Cinema Paradiso", year: 1988 },
{ title: "The Lives of Others", year: 2006 },
{ title: "Grave of the Fireflies", year: 1988 },
{ title: "Paths of Glory", year: 1957 },
{ title: "Django Unchained", year: 2012 },
{ title: "The Shining", year: 1980 },
{ title: "WALL·E", year: 2008 },
{ title: "American Beauty", year: 1999 },
{ title: "The Dark Knight Rises", year: 2012 },
{ title: "Princess Mononoke", year: 1997 },
{ title: "Aliens", year: 1986 },
{ title: "Oldboy", year: 2003 },
{ title: "Once Upon a Time in America", year: 1984 },
{ title: "Witness for the Prosecution", year: 1957 },
{ title: "Das Boot", year: 1981 },
{ title: "Citizen Kane", year: 1941 },
{ title: "North by Northwest", year: 1959 },
{ title: "Vertigo", year: 1958 },
{ title: "Star Wars: Episode VI - Return of the Jedi", year: 1983 },
{ title: "Reservoir Dogs", year: 1992 },
{ title: "Braveheart", year: 1995 },
{ title: "M", year: 1931 },
{ title: "Requiem for a Dream", year: 2000 },
{ title: "Amélie", year: 2001 },
{ title: "A Clockwork Orange", year: 1971 },
{ title: "Like Stars on Earth", year: 2007 },
{ title: "Taxi Driver", year: 1976 },
{ title: "Lawrence of Arabia", year: 1962 },
{ title: "Double Indemnity", year: 1944 },
{ title: "Eternal Sunshine of the Spotless Mind", year: 2004 },
{ title: "Amadeus", year: 1984 },
{ title: "To Kill a Mockingbird", year: 1962 },
{ title: "Toy Story 3", year: 2010 },
{ title: "Logan", year: 2017 },
{ title: "Full Metal Jacket", year: 1987 },
{ title: "Dangal", year: 2016 },
{ title: "The Sting", year: 1973 },
{ title: "2001: A Space Odyssey", year: 1968 },
{ title: "Singin' in the Rain", year: 1952 },
{ title: "Toy Story", year: 1995 },
{ title: "Bicycle Thieves", year: 1948 },
{ title: "The Kid", year: 1921 },
{ title: "Inglourious Basterds", year: 2009 },
{ title: "Snatch", year: 2000 },
{ title: "3 Idiots", year: 2009 },
{ title: "Monty Python and the Holy Grail", year: 1975 }
];
I used the approach based on the limitTags option, layout effect hook, and DOM measurements.
Here is my custom hook implementation. You can use it like:
const autocompleteRef = useRef(); // don't forget to set the ref
const tagsLimit = useTagsLimit(autocompleteRef, memoizedTags);
The approach has a downside in relation to the original question. It is that autocomplete turn back to multiline when focused. So, some tuning may be required
import { useLayoutEffect, useState } from 'react';
const NO_LIMIT = -1;
const KEEP_SOME_SPACE = 200;
export default function useTagsLimit(autocompleteRef, tags) {
const [tagsLimit, setTagsLimit] = useState(NO_LIMIT);
useLayoutEffect(() => {
const autocompleteDOMNode = autocompleteRef.current;
// next line code looks a bit unreliable, alternatively you can try using css class selector
const tagsContainerDOMNode =
autocompleteDOMNode?.firstElementChild?.firstElementChild;
const autocompleteWidth = autocompleteDOMNode.getBoundingClientRect().width;
const tagsWidths = getDomNodeListWidths(tagsContainerDOMNode?.children);
const fittingLength = findFittingLength(
tagsWidths,
autocompleteWidth - KEEP_SOME_SPACE
);
setTagsLimit(
fittingLength < tags.length ? fittingLength : NO_LIMIT
);
return () => {
setTagsLimit(NO_LIMIT);
};
}, [tags]);
return tagsLimit;
}
function findFittingLength(items, fitValue) {
let cumulativeTotal = 0;
for (let itemIndex = 0; itemIndex < items.length; itemIndex += 1) {
cumulativeTotal += items[itemIndex];
if (cumulativeTotal > fitValue) {
return itemIndex;
}
}
return items.length;
}
function getDomNodeListWidths(domNodeList) {
if (!domNodeList) {
return [];
}
return Array.from(domNodeList).map(
(element) => element.getBoundingClientRect().width
);
}
You can probably use something like this if you want to limit the tags all the time not only when it's not focused because in the official documentation there is an example with limitTags property but it's not limiting the tags when input is focused. I am limiting the tags to 1 in my example but you can adjust to your needs.
<Autocomplete
// ...other props here
renderTags={(value, getTagProps) => {
const numTags = value.length;
const limitTags = 1;
return (
<>
{value.slice(0, limitTags).map((option, index) => (
<Chip
{...getTagProps({ index })}
key={index}
label={option.name}
/>
))}
{numTags > limitTags && ` +${numTags - limitTags}`}
</>
);
}}
/>
Use this inside your css. This will not expand the height of Autocomplete.
.MuiAutocomplete-inputRoot {
flex-wrap: nowrap !important;
}
.Mui-focused {
flex-wrap: wrap !important;
}
You can also adjust the width of chips
.MuiAutocomplete-tag {
max-width: 100px !important;
}
Reference answer
Newbie to react here and I am building a search page in a multi-form app where users can search for a student with first name, last name or student ID in an autocomplete textfield.
Now, options for the autocomplete textfields are successfully passed and are displayed once the cursor is in the textfield but typing or actually selecting a value doesn't seem to affect the value of the textfield.
Code below
<Autocomplete
id="auto-complete"
options={values.suggestions}
getOptionLabel={option => option.stuId+" "+ option.fName+" "+option.lName}
autoComplete
includeInputInList
fullWidth
renderInput={params => (
<TextField {...params} margin="normal" fullWidth />)}
/>
values are passed through props.
Intended outcome is for the 'searchKeyword' in state to be updated with the stuId of the student selected and for the autocomplete textfield to display it. is there an onChange or default value function I could use?
is this what you're looking for?
<Autocomplete
...
value={this.state.value}
onChange={(event, value) => this.setState({ value })}
...
/>
You can get value inside option state in hooks
/* eslint-disable no-use-before-define */
import React, { useState } from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
export default function ComboBox() {
const [option, setOption] = useState("");
return (
<Autocomplete
id="combo-box-demo"
options={top10Films}
getOptionLabel={(option) => option.title}
style={{ width: 300 }}
value={option}
onChange={(e, v) => {
setOption(v);
}}
renderInput={(params) => (
<TextField {...params} label="Combo box" variant="outlined" />
)}
/>
);
}
// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top
const top10Films = [
{ title: "The Shawshank Redemption", year: 1994 },
{ title: "The Godfather", year: 1972 },
{ title: "The Godfather: Part II", year: 1974 },
{ title: "The Dark Knight", year: 2008 },
{ title: "12 Angry Men", year: 1957 },
{ title: "Schindler's List", year: 1993 },
{ title: "Pulp Fiction", year: 1994 },
{ title: "The Lord of the Rings: The Return of the King", year: 2003 },
{ title: "The Good, the Bad and the Ugly", year: 1966 },
{ title: "Fight Club", year: 1999 }
];
refer to this codesandbox
I have a long form which has mixed type of column. I mean some field will be of width 100% some field will be around 33% so that i can show 3 fields in a column and so on. This I could do without iterating, but how can it be done if the fields are shown with iteration to avoid code repetition? I have created a sandbox either and I am not using third party css framework like bootstrap just a flex or may be grid would work.
Here is how i have tried:
const formFields = [
{ id: 1, name: "first_name", component: TextField, label: "First Name" },
{ id: 1, name: "last_name", component: TextField, label: "Last Name" },
{ id: 1, name: "age", component: TextField, label: "Age" },
{ id: 1, name: "city", component: TextField, label: "City" },
{ id: 1, name: "state", component: TextField, label: "State" },
{ id: 1, name: "country", component: TextField, label: "Country" }
];
const FlexibleForm = () => {
return (
<React.Fragment>
<Form onSubmit={() => console.log("something")}>
<FlexRow>
<FlexColumn size={12}>
{formFields.map(({ id, name, label, component }) => {
return (
<Field
key={id}
name={name}
label={label}
component={component}
/>
);
})}
</FlexColumn>
</FlexRow>
<h2>Another without iteration</h2>
<FlexRow>
<FlexColumn size={6}>
<Field name="first_name" label="First Name" component={TextField} />
</FlexColumn>
<FlexColumn size={6}>
<Field name="last_name" label="Last Name" component={TextField} />
</FlexColumn>
</FlexRow>
<FlexRow>
<FlexColumn size={12}>
<Field name="age" label="Age" component={TextField} />
</FlexColumn>
</FlexRow>
<FlexRow>
<FlexColumn size={4}>
<Field name="city" label="City" component={TextField} />
</FlexColumn>
<FlexColumn size={4}>
<Field name="state" label="State" component={TextField} />
</FlexColumn>
<FlexColumn size={4}>
<Field name="country" label="Country" component={TextField} />
</FlexColumn>
</FlexRow>
</Form>
</React.Fragment>
);
};
export default reduxForm({
form: "form"
})(FlexibleForm);
export const FlexRow = styled.div`
display: flex;
div {
margin-right: 10px;
}
`;
export const FlexColumn = styled.div`
width: ${props => (props.size / 12) * 100}vw;
`;
Here is the codesandbox link https://codesandbox.io/s/l7lm1qp0pq
This is <FlexRow> component
const FlexRow = props => {
return (
<div style={{ display: "flex" }}>
{props.elements.map((element, index) => {
const obj = element.component;
return (
<FlexColumn key={index} size={Math.round(12 / props.elements.length)}>
<Field
name={element.name}
label={element.label}
component={obj[Object.keys(obj)[0]]}
/>
</FlexColumn>
);
})}
</div>
);
};
This is <FlexColumn> component
const FlexColumn = styled.div`
width: ${props => (props.size / 12) * 100}vw;
margin-right: 10px;
`;
Your <FlexibleForm> should look like this :
const FlexibleForm = () => {
return (
<React.Fragment>
<Form onSubmit={() => console.log("something")}>
<FlexRow
elements={[
{ name: "first_name", label: "First Name", component: {TextField} },
{ name: "last_name", label: "Last Name", component: {TextField} }
]}
/>
<FlexRow
elements={[{ name: "age", label: "Age", component: {TextField} }]}
/>
<FlexRow
elements={[
{ name: "city", label: "City", component: {TextField} },
{ name: "state", label: "State", component: {TextField} },
{ name: "country", label: "Country", component: {TextField} }
]}
/>
</Form>
</React.Fragment>
);
};
The component <FlexRow> will loop inside his props elements and will create a <FlexColumn> with the good size with the good attributes.
Litteraly, it did loop inside the props elements which is an array and return the size (12 divided by the number of rows you have), you can change this value if you want with a simple condition like :
element.size ? element.size : Math.round(12 / props.elements.length)
and add a size element in your elements array
{ name: "first_name", label: "First Name", size: 6, component: {TextField} }
Then it add the label and the name. And finally it add the component you choosed, in this example it's <TextField>
This will avoid you from repeating your code.
Here's the CodeSandBox to show you the example !
Wish I did helped you.
There is no much point of using flex if you are not using flex-wrap.
If you want to do it with simple float:
const formFields = [
{ id: 1, name: "first_name", component: TextField, label: "First Name",size: 6 },
{ id: 1, name: "last_name", component: TextField, label: "Last Name", size: 6 },
{ id: 1, name: "age", component: TextField, label: "Age", size: 12 },
{ id: 1, name: "city", component: TextField, label: "City", size: 4 },
{ id: 1, name: "state", component: TextField, label: "State", size: 4 },
{ id: 1, name: "country", component: TextField, label: "Country", size: 4 }
];
const Form = styled.form`
width: 100%;
`;
const FlexColumn = styled.div`
float: left;
width: ${props => (props.size / 12) * 100}%;
`;
const FlexibleForm = () => (
<React.Fragment>
<Form onSubmit={() => console.log("something")}>
{formFields.map(({ id, name, label, component, size }) => {
return (
<FlexColumn size={size}>
<Field key={id} name={name} label={label} component={component} />
</FlexColumn>
);
})}
</Form>
</React.Fragment>
);