I am trying to use the Multiple prop with Autocomplete in Material UI. But every time I try to add it in it breaks my code. Seems to have an issue with my onChange prop, but I cannot figure out how to make them work together. Basically I am trying to display multiple values as well as the information inside the array on my page. Here is my code below. Thanks in advance!
type Movie = {
label: string
year: number
}
export default function ComboBox() {
const [value, setValue] = useState<string | null>(null)
const [movie, setMovie] = useState<Movie | null>(null)
console.log({ movie });
return (
<Grid container padding='32px'>
<Stack direction='row' spacing={2} divider={<Divider orientation='vertical' flexItem sx={{ width: 'auto' }} />}>
<Autocomplete
sx={{ width: 300 }}
options={top100Films}
renderInput={(params) => <TextField {...params}
label='Skills' />}
value={movie}
onChange={(event: any, newValue: Movie | null) => setMovie(newValue)}
/>
<Paper sx={{ width: 300 }} elevation={4} >
<Typography fontSize='40px'> {movie === null?"": movie.label} </Typography>
</Paper>
<Paper sx={{ width: 300 }} elevation={4}>
<Typography fontSize='40px'>
{movie === null?"": movie.year}
</Typography>
</Paper>
</Stack>
</Grid>
);
}
Related
I would like to know if it is possible to customise the above example so that the list would have column headers such as Title and duration. I have tried to see if I could get it to work using a custom ListBox, but no such luck. Below is a snippet of my own code:
const PopperMy = function (props: PopperProps) {
return <Popper {...props} style={{ width: 500 }} placement='bottom-start' />;
};
return (
<Autocomplete
filterOptions={(x) => x}
getOptionLabel={(option: Record<string, unknown>) => `${option.order}, ${option.name}, ${option.email}, ${option.phone}, ${option.location}`}
renderOption={(props, option: any) => {
return (
<li {...props} key={option.ID} >
Order: {option.order}, Name: {option.name}, Email: {option.email}, Phone: {option.phone}, Location: {option.location}, Status: {option.status}
</li>
);
}}
options={results}
value={selectedValue}
clearOnBlur={false}
freeSolo
PopperComponent={PopperMy}
disableClearable={true}
includeInputInList
onChange={(ev, newValue) => {
setSelectedValue(newValue);
}}
onInputChange={(ev, newInputValue) => {
setInputValue(newInputValue);
}}
renderInput={(params) => (
<TextField {...params} />
)} /> )
this is achievable by customizing the popper component. In your case, something like `
const PopperMy = function (props) {
const { children, ...rest } = props;
return (
<Popper {...rest} placement="bottom-start">
<Box display="flex" justifyContent="space-between" px="16px">
<Typography variant="h6">Title</Typography>
<Typography variant="h6">Year</Typography>
........... rest of the titles
</Box>
{props.children}
</Popper>
);
};
`
would work. Here is a working example i have created - https://codesandbox.io/s/heuristic-golick-4sv24u?file=/src/App.js:252-614
what my task is I am using a table with two different conditions like in the first table whatever data coming I will show that in the first table and in the second table whatever row I select in the first table that I want to show in the second table as the second table I called select summary so my task is in the first table whatever row I selected in need to how that row in the second table I am using same table component for this for better you can see CodeSandBox link
import React, { useState, useMemo, useEffect } from "react";
import {
Grid,
makeStyles,
CardContent,
Card,
Box
} from "#material-ui/core";
import EnhancedTable from "./EnhancedTable";
const useStyles = makeStyles((theme) => ({
root: {
padding: theme.spacing(0, 2, 2),
},
formGrid: {
padding: theme.spacing(2),
},
cardColor: {
borderColor: "#0bb7a7",
},
}));
function AddToExclusionList() {
const classes = useStyles();
const [sanctionsList, setSanctionsList] = useState([]);
const updateListsRow = ({ index, value, row }, listType) => {
switch (listType) {
case "sanctions":
setSanctionsList((prevState) => {
prevState[index].status = value;
return [...prevState];
});
break;
default:
}
};
return (
<Grid className={classes.root}>
<Grid item xs={12} style={{ textTransform: "capitalize" }}>
<Card className={classes.cardColor} variant="outlined">
<CardContent>
<>
<EnhancedTable
show={true}
step="first"
/>
</>
</CardContent>
</Card>
</Grid>
<Box mt={3}></Box>
<Grid item xs={12} style={{ textTransform: "capitalize" }}>
<Card className={classes.cardColor} variant="outlined">
<CardContent>
<>
<h3>summary table</h3>
<EnhancedTable
checkboxToggle={(rowDetails) => {
updateListsRow(rowDetails, "sanctions");
}}
/>
</>
</CardContent>
</Card>
</Grid>
</Grid>
);
}
export default AddToExclusionList;
CodeSandBox Link
You've achieved your goal very weird! Anyway, based on your code in codesandbox. You need to add a state to AddToExclusionList component, like this:
const [newRows, setNewRows] = useState([]);
const setSummaryRows = (selectedRows) => {
const copy = [...rows];
const filteredRows = copy.filter((x) => selectedRows.includes(x.name));
setNewRows(filteredRows);
};
We need the mentioned state to update the summary table's rows.
Also add rows and setNewRows prop to EnhancedTable and give it rows from out of the component. In addition move rows and createData to App.js. So you should use EnhancedTable in App.js same as bellow:
<Grid className={classes.root}>
<Grid item xs={12} style={{ textTransform: "capitalize" }}>
<Card className={classes.cardColor} variant="outlined">
<CardContent>
<>
<h3>first table</h3>
<EnhancedTable
// passing data for rendering table according condition
step="first"
rows={rows}
setNewRows={(selected) => {
setSummaryRows(selected);
}}
/>
</>
</CardContent>
</Card>
</Grid>
<Box mt={3}></Box>
<Grid item xs={12} style={{ textTransform: "capitalize" }}>
<Card className={classes.cardColor} variant="outlined">
<CardContent>
<>
<h3>summary table</h3>
<EnhancedTable
// trying to pasing selected data
rows={newRows}
setNewRows={() => {}}
/>
</>
</CardContent>
</Card>
</Grid>
</Grid>
And the last part is using useEffect based on selected in EnhancedTable component:
useEffect(() => {
setNewRows(selected);
}, [selected]);
I have a question on how to use this data to map the headers to it's corresponding values and put that on the UI
This is how the data is structured:
{
"data": {
"details": [
{
"address_line_1": "C O Cwtsatotravel",
"address_line_2": "Not Available",
"city_name": "Arlington",
"state_name": "-",
"country_name": "Japan",
"postal_code": "22203",
"phone_number": "7638527755",
}
]
}
}
This is what I am trying to do in react
const profile_info = data?.details;
const profileHeaders = [
'Address1',
'Address2'
'City',
'State',
'Postal Code',
'Country',
'Phone',
];
return (
<Grid
id="top-card"
className={classes.mainContainer}
container
style={{
marginBottom: '4px',
}}
>
{/* <Grid item md={11} lg={11} id="item-card"> */}
<Grid container item>
<Typography variant="subtitle1">
{profile_info.agency_name}
</Typography>
</Grid>
<Grid
container
style={{
backgroundColor: '#f9f9f9',
}}
>
{profileHeaders.map((v) => (
<Grid
item
style={{
padding: '0px 4px',
}}
>
<Typography className={classes.profileData} gutterBottom={true}>
{v}
</Typography>
<Typography className={classes.profileData}>
{' '}
{profile_info[v]}
</Typography>
</Grid>
))}
</Grid>
</Grid>
);
When I do this, it's getting me all blank values on the UI for the headers
Please help, thank you !
Encode your headers using the same [as in data] keys:
const headers = {
"address_line_1": "Address1",
"address_line_2": "Address2",
"city_name": "City",
later, you can list it
Object.entries(headers).forEach(([key, value]) => console.log(`${key}: ${value}`));
console.log(data) to see its structure and use const to 'alias' iterable (object with props or array) element:
// choose the right data source - depends on what logged out
// console.log(data);
// if(data) console.log(data.details); //... step by step
// const profile_info = data?.details;
// const profile_info = data?.data.details;
const profile_info = data?.details[0]; // object in array here
render values from both headers and profile_info
Object.entries(headers).forEach(([key, value]) => (
<Grid
key={key}
item
style={{
padding: '0px 4px',
}}
>
<Typography className={classes.profileData} gutterBottom={true}>
{value}
</Typography>
<Typography className={classes.profileData}>
{' ??? use css instead! '}
{profile_info[key]}
</Typography>
</Grid>
or you can do the same using .map (you can use index if required)
Object.keys(headers).map((key, idx) => (
<Element key={key}>
<Name title={headers[key]} />
<Value data={profile_info[key]} />
There is some crucial erros on your code:
const profile_info = data?.details;
Your details are stored on property data.data.details, not data.details
So fix this, first:
const profile_info = data?.data.details;
The items in ProfileHeaders are not mapped like properties in profile_info: you have a profile_info['address_line_1'], but not profile_info['Address1'], wich is what you are trying to do in your component.
To make this work the way you want, you should map title and property correctly.
const profileHeaders = [
{
title: "Address1",
property: "address_line_1"
},
{
title: "Address2",
property: "address_line_2"
},
// map all other properties like above.
]
then you can go for that:
{profileHeaders.map((v) => (
<Grid
item
style={{
padding: '0px 4px',
}}
>
<Typography className={classes.profileData} gutterBottom={true}>
{v.title}
</Typography>
<Typography className={classes.profileData}>
{' '}
{profile_info[v.property]}
</Typography>
</Grid>
))}
I am not checking if profile_info is undefined, but you must do it in your component to avoid errors.
I am creating the following component:
It will contain an array of objects, where each object is a prescription, with the medicine name from the select and a TextField for the Dosis.
My problem is that the TextField loses focus on every onChange() and is very frustrating because it cannot be edited on a single focus.
This is my component :
const MedicineSelect = ({ medications, setMedications, ...props }) => {
const { medicines } = useMedicines()
const classes = useStyles()
const handleChange = (index, target) => {
// setAge(event.target.value)
const newMedications = cloneDeep(medications)
newMedications[index][target.name] = target.value
setMedications(newMedications)
}
const handleAddMedicine = () => {
const newMedications = cloneDeep(medications)
newMedications.push({ medicine: '', dosis: '', time: '' })
setMedications(newMedications)
}
const handleDeleteMedicine = (index) => {
console.log('DELETE: ', index)
const newMedications = cloneDeep(medications)
newMedications.splice(index, 1)
setMedications(newMedications)
}
return (
<Paper style={{ padding: 5 }}>
<List>
{medications.map((medication, index) => (
<ListItem key={nanoid()} divider alignItems='center'>
<ListItemIcon>
<Tooltip title='Eliminar'>
<IconButton
className={classes.iconButton}
onClick={() => handleDeleteMedicine(index)}
>
<HighlightOffOutlinedIcon />
</IconButton>
</Tooltip>
</ListItemIcon>
<FormControl className={classes.formControl}>
<InputLabel
id={`${index}-select-${medication}-label`}
>
Medicamento
</InputLabel>
<Select
labelId={`${index}-select-${medication}-label`}
id={`${index}-select-${medication}`}
name='medicine'
value={medication.medicine}
onChange={(event) =>
handleChange(index, event.target)
}
>
{medicines.map((medicine) => (
<MenuItem
key={nanoid()}
value={medicine.name}
>
{medicine.name}
</MenuItem>
))}
</Select>
</FormControl>
<TextField
// fullWidth
id={`${index}-text-${medication}`}
label='Dosis'
name='dosis'
onChange={(event) =>
handleChange(index, event.target)
}
value={medication.dosis}
/>
</ListItem>
))}
<Button onClick={handleAddMedicine}>+ agregar</Button>
</List>
</Paper>
)
}
And here is where I set the component:
const [medications, setMedications] = useState([
{ medicine: '', dosis: '', time: '' },
])
...
<Grid item md={12} xs={12}>
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls='panel1a-content'
id='panel1a-header'
>
<Typography variant='h4'>
Tratamiento:
</Typography>
</AccordionSummary>
<AccordionDetails>
<Container disableGutters>
<MedicineSelect
medications={medications}
setMedications={setMedications}
/>
</Container>
</AccordionDetails>
</Accordion>
</Grid>
...
Adding and removing objects from the array works perfect. selecting the medicine from the select, also works perfect. the only problem I have is when editing the Dosis TextField, with every character, the focus is lost and I have to click again on the TextField.
Please help me getting this fixed!!!
After searching a lot, finally I found the solution. Actually when using nanoid() to create unique keys, on every state update React re-renders all components and since the id of both the List and the TextField component are regenerated by nanoid on every render, React loses track of the original values, that is why Focus was lost.
What I did was keeping the keys unmuttable:
<ListItem key={`medication-${index}`} divider alignItems='center'>
and
<TextField
key={`dosis-${index}`}
fullWidth
// id={`${index}-dosis-${medication}`}
label='Dosis'
name='dosis'
onChange={(event) =>
handleChange(index, event.target)
}
value={medication.dosis}
/>
I've been looking at the (https://material-ui.com/api/autocomplete/) API for the autocomplete component but I can't seem to find a way (from my limited knowledge of javascript) to only display a certain number of options below the TextField.
I'm trying to incorporate a search function with over 7,000 data but I don't want to display all of it at once. How can I limit the options to at most 10 suggestions?
This can be done using filterOptions prop and createFilterOptions function.
...
import { Autocomplete, createFilterOptions } from "#material-ui/lab";
const OPTIONS_LIMIT = 10;
const defaultFilterOptions = createFilterOptions();
const filterOptions = (options, state) => {
return defaultFilterOptions(options, state).slice(0, OPTIONS_LIMIT);
};
function ComboBox() {
return (
<Autocomplete
filterOptions={filterOptions}
id="combo-box-demo"
options={top100Films}
getOptionLabel={(option) => option.title}
style={{ width: 300 }}
renderInput={(params) => (
<TextField {...params} label="Combo box" variant="outlined" />
)}
/>
);
}
GitHub issue
Ciao, you could use filterOptions as explained by #bertdida or you could directly filter options array in this way:
const ELEMENT_TO_SHOW = 10;
...
<Autocomplete
id="combo-box-demo"
options={top100Films.filter((el, i) => { // here add a filter for options
if (i < ELEMENT_TO_SHOW) return el;
})}
getOptionLabel={(option) => option.title}
style={{ width: 300 }}
renderInput={(params) => (
<TextField {...params} label="Combo box" variant="outlined" />
)}
/>
Here a codesandbox example.
I started with bertida's answer but then I found out createFilterOptions can do it already (see https://material-ui.com/components/autocomplete/#createfilteroptions-config-filteroptions for other interesting options)
const OPTIONS_LIMIT = 10;
const filterOptions = createFilterOptions({
limit: OPTIONS_LIMIT
});
function ComboBox() {
return (
<Autocomplete
filterOptions={filterOptions}
id="combo-box-demo"
options={top100Films}
getOptionLabel={(option) => option.title}
style={{ width: 300 }}
renderInput={(params) => (
<TextField {...params} label="Combo box" variant="outlined" />
)}
/>
);
}
See codesandbox example