MUI : Out-of-range value `X` for the select component - javascript

I'm making a glossary where each of the definitions are a cards that can be flipped (CardFlip) I build an array where I send for each card, the data via props to my component "CardFlip" dealing with the actual construction of cards with my data
This is my first component sending everything :
<div>
{glossaire.map((carte, index) => (
<Fragment key={carte.dat_id}>
<CardFlip item={carte} tags={tags} />
</Fragment>
))}
</div>
First prop ,"item", contains information such as: a title, a definition, a search tag
Second prop, "tags", is the list of tags that a definition can have, a definition can have only one tag, right now only those tags are available : "Application", "Entreprise", "Technique" and "Télécom"
And here is the code for my second component (only the interesting part):
export default function CardFlip = ({ item, user, tags }) => {
// -- [Variables] --
// Flip front / back
const [isFlipped, setIsFlipped] = useState(false);
// Storage
const [titreDef, setTitreDef] = useState("");
const [definitionDef, setDefinitionDef] = useState("");
const [tagSelected, setTagSelected] = useState("");
// Flag for error
const [errorTitre, setErrorTitre] = useState(false);
const [errorDefinition, setErrorDefinition] = useState(false);
const [errorSelect, setErrorSelect] = useState(false);
console.log(item.dat_tag);
console.log(tags);
// -- [Handlers] --
// UPDATE
const handleTitre = (data) => {
setTitreDef(data);
setErrorTitre(false);
};
const handleDefinition = (data) => {
setDefinitionDef(data);
setErrorDefinition(false);
};
const handleSelect = (event) => {
const {
target: { value },
} = event;
setTagSelected(value);
setErrorSelect(false);
}
return (
<Grow in style={{ transformOrigin: "0 0 0" }} {...{ timeout: 1000 }}>
<div style={{ display: "flex", padding: "10px" }}>
<ReactCardFlip
isFlipped={isFlipped}
flipDirection="horizontal"
style={{ height: "100%" }}
>
<div
className={style.CardBack}
style={{ display: "flex", height: "100%" }}
>
<Card className={style.mainCard}>
<CardActions className={style.buttonFlipCard}>
<Tooltip title="Retour">
<IconButton
className={style.iconFlipCard}
disableRipple
onClick={() => setIsFlipped((prev) => !prev)}
>
<ChevronLeftIcon />
</IconButton>
</Tooltip>
</CardActions>
<CardContent>
<div className={style.divTagBack}>
<FormControl
sx={{width: "90%"}}
>
<InputLabel
id="SelectLabel"
sx={{display: "flex"}}
>
{<TagIcon />}
{" Tag"}
</InputLabel>
<Select
labelId="SelectLabel"
label={<TagIcon /> + " Tag"}
renderValue={(selected) => (
<Chip
onMouseDown={(event) => {
event.stopPropagation();
}}
key={selected}
label={selected}
icon={<TagIcon />}
/>
)}
defaultValue={item.dat_tag}
onChange={handleSelect}
>
{tags && tags.map((tag) => (
<MenuItem key={tag.dat_tag} value={tag.dat_tag}>
{tag.dat_tag}
</MenuItem>
))}
</Select>
</FormControl>
</div>
</CardContent>
</Card>
</div>
</ReactCardFlip>
</div>
</Grow>
);
};
When the user returns the card, he can change the title, description and tag of the chosen card.
My problem is with the Select.
In order to display the selected tag before any modification, I display the tag in defaultValue={item.dat_tag}
(Also tried with value and not defaultValue)
Then with my second prop, I build the list of my menu.
This is where I get my warning (which seems to extend the rendering time of the page considerably (Since it load / render like +100 definitions, getting a warning for every definition)
MUI: You have provided an out-of-range value Entreprise for the select component.
Consider providing a value that matches one of the available options or ''. The available values are "".
This is an example of what a console.logs told me about my props :
item.dat_tag
Entreprise
tags
0: {dat_tag: "Applicatif"}
1: {dat_tag: "Entreprise"}
2: {dat_tag: "Technique"}
3: {dat_tag: "Télécom"}
I already looked at several posts saying to put in a string variable my tag data (item.dat_tag) or to display my menu when it is not empty. No change.

Related

How to clear data on Select multiple when typing on another textfield using reactjs

This is my current code, what I want here is after selecting on Select option (CHIP) and if the user type on the textfield I want to clear what the user selected on CHIP, What should I do to get what i want functionality?
const names = [
'Oliver Hansen',
'Van Henry',
'April Tucker',
];
function getStyles(name, personName, theme) {
return {
fontWeight:
personName.indexOf(name) === -1
? theme.typography.fontWeightRegular
: theme.typography.fontWeightMedium,
};
}
export default function MultipleSelectChip() {
const theme = useTheme();
const [personName, setPersonName] = React.useState([]);
const handleChange = (event) => {
const {
target: { value },
} = event;
setPersonName(
// On autofill we get a stringified value.
typeof value === 'string' ? value.split(',') : value
);
};
const handleChangeTextField = (event) => {
setPersonName(null);
};
return (
<div>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="demo-multiple-chip-label">Chip</InputLabel>
<Select
labelId="demo-multiple-chip-label"
id="demo-multiple-chip"
multiple
value={personName}
onChange={handleChange}
input={<OutlinedInput id="select-multiple-chip" label="Chip" />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value) => (
<Chip key={value} label={value} />
))}
</Box>
)}
MenuProps={MenuProps}
>
{names.map((name) => (
<MenuItem
key={name}
value={name}
style={getStyles(name, personName, theme)}
>
{name}
</MenuItem>
))}
</Select>
<TextField
variant="outlined"
label="Type anything to remove the value of Chip"
onChange={handleChangeTextField} />
</FormControl>
</div>
This is my current code, what I want here is after selecting on Select option (CHIP) and if the user type on the textfield I want to clear what the user selected on CHIP, What should I do to get what i want functionality?
I would set your textfield to be controlled (ie backed by a state variable) and add an effect hook to watch it.
When it receives a value, clear the selected names by setting personNames back to an empty array.
const [text, setText] = useState("");
useEffect(() => {
if (text) {
setPersonName([]);
}
}, [text]);
const handleChangeTextField = ({ target: { value } }) => {
setText(value);
};
<TextField
value={text}
variant="outlined"
label="Type anything to remove the value of Chip"
onChange={handleChangeTextField}
/>
You might also want to clear the text field when selecting names by adding this into handleChange...
setText("");

How do I pass a variable and the function to update its state as a prop in React?

If I have variable as such:
const [APIKey, setAPIKey] = useState([])
Can I group these into 1 variable and pass it along as a prop?
const passAPI = {APIKey, setAPIKey}
Here is how I'm attempting to pass it:
const Home = () => {
const [APIKey, setAPIKey] = useState([])
const [Symbols, setSymbols] = useState([])
const [Endpoint, setEndpoint] = useState([])
const [Base, setBase] = useState([])
const passAPI = {APIKey, setAPIKey}
const passSymbols = {Symbols, setSymbols}
const passEndpoint = {Endpoint, setEndpoint}
const passBase = {Base, setBase}
return (
<Grid container spacing='8' className='HomeWrapper'>
<Grid item sx={{ display: { xs: 'none', sm: 'flex' }, width: '50%' }} className='x1'>
<Paper elevation='6' className='HomePaper' sx={{ width: '100%'}}>
<TopLeft passAPI={passAPI} passSymbols={passSymbols} passEndpoint={passEndpoint} passBase={passBase}/>
</Paper>
</Grid>
...
And here is an example of how I was trying to use it:
const TopRight = ({passAPI, passSymbols, passEndpoint, passCurrency}) => {
return (
<Box className='TopRightWrapper' sx={{ display: 'flex', flexDirection: 'column' }} pl={2} pt={1} pb={1}>
<FormControl onSubmit={passAPI}>
<Box>
<InputLabel htmlFor="my-input">API Key</InputLabel>
<Input id="my-input" aria-describedby="my-helper-text" />
<button type='submit'>click</button>
</Box>
</FormControl>
I would want to use the setAPIKey function that is found in the passAPI variable. How do I go about this?
As I can see passAPI in your case is an object. So to get setState from passAPI you should use "passAPI.setAPIKey" in onSubmit.
So it should be like that:
<FormControl onSubmit={passAPI.setAPIKey}>
<Box>
<InputLabel htmlFor="my-input">API Key</InputLabel>
<Input id="my-input" aria-describedby="my-helper-text" />
<button type='submit'>click</button>
</Box>
</FormControl>
Also FormControl API does not have onSubmit event, you should use "form" tag instead.
Through form onSumbit event argument you would be able to access "target" property which would have form elements inside, and then you will access value of that input element. Something like that
<form
onSubmit={(e) => {
e.preventDefault();
console.log("e", e.target[0].value);
passAPI.setAPIKey(e.target[0].value)
}}
>
Let me know if it helps.

Material-UI TextField loses focus on every onChange

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}
/>

How to target single item in list with onClick when mapping JSON array in React

I have a JSON file with an array of objects and each object consists of "Question" and "Answer" (I'm creating an FAQ section). What I'm doing is mapping over the array and displaying the list of questions, which works just fine. Next to each question is an icon and I want the icon to change when I click on it but it is changing EVERY icon in the list instead of just that one item that was clicked on.
I'm using Material UI and hooks and this is how I have my handleClick set up:
const [click, setClick] = useState(true);
const handleClick = () => {
setClick(!click);
};
This is how I have the array mapping set up:
<List
style={{
maxHeight: 430,
width: 500,
overflow: 'auto',
border: '1px solid black',
}}
>
{faqdata.map((item) => (
<ListItem style={{ cursor: 'pointer' }}>
<ListItemIcon>
{click ? <AddIcon /> : <RemoveIcon />}
</ListItemIcon>
<ListItemText primary={item.Question} onClick={handleClick} />
</ListItem>
))}
</List>
How can I make it to where the icon changes on only the list item that I click on instead of every list item in the list? Is my onClick in the incorrect spot? Any help would be greatly appreciated. Thanks!
Issue
You are using a single boolean value to store a "clicked" state, and all your mapped UI uses that single state to cue from.
Solution
Assuming you would like multiple items to be clicked, and also assuming your mapped data is static (i.e. the faqData isn't added to, removed from, or sorted) then using the mapped index to toggle the "clicked" state is acceptable. use an object to store "clicked" indices and update the handleClick callback to toggle the state. For this use case I like to make the callback a curried handler to enclose in scope the value I wish to use in the callback.
const [clickedIndex, setClickedIndex] = useState({});
const handleClick = (index) => () => {
setClickedIndex(state => ({
...state, // <-- copy previous state
[index]: !state[index] // <-- update value by index key
}));
};
...
<List
style={{
maxHeight: 430,
width: 500,
overflow: 'auto',
border: '1px solid black',
}}
>
{faqdata.map((item, index) => (
<ListItem style={{ cursor: 'pointer' }}>
<ListItemIcon>
{clickedIndex[index] ? <AddIcon /> : <RemoveIcon />} // <-- check if index is truthy in clickedIndex state
</ListItemIcon>
<ListItemText
primary={item.Question}
onClick={handleClick(index)} // <-- pass index to handler
/>
</ListItem>
))}
</List>
Is this what you're looking for?
import { useState } from "react";
import "./styles.css";
const faqdata = [
{ Question: "Q1", Answer: "A1" },
{ Question: "Q2", Answer: "A2" },
{ Question: "Q3", Answer: "A3" },
{ Question: "Q4", Answer: "A4" }
];
const AddIcon = () => <span class="icon">+</span>;
const RemoveIcon = () => <span class="icon">☓</span>;
function ListItem({ d }) {
const [checked, setChecked] = useState(false);
return (
<li
onClick={() => {
setChecked(!checked);
}}
>
{checked ? <RemoveIcon /> : <AddIcon />}
{d.Question}
</li>
);
}
function List() {
return (
<ul>
{faqdata.map((d) => {
return <ListItem d={d} />;
})}
</ul>
);
}
You can try it out here
The problem with the current approach is that there's only one variable to store the added/removed status of every question. So, when the click boolean updates, it updates the state of all elements.
In the code shared above, the ListItem component is responsible for maintaining the added/removed status of each question separately. So, one item in the list can change without affecting the other.
It's one of my test. You should save selected ids and check if the id exists in that array.
const [selectedfaqdataIds, setSelectedfaqdataIds] = useState([]);
const handleSelect = (event, id) => {
const selectedIndex = selectedfaqdataIds.indexOf(id);
let newselectedfaqdataIds = [];
if (selectedIndex === -1) {
newselectedfaqdataIds = newselectedfaqdataIds.concat(selectedfaqdataIds, id);
} else if (selectedIndex === 0) {
newselectedfaqdataIds = newselectedfaqdataIds.concat(selectedfaqdataIds.slice(1));
} else if (selectedIndex === selectedfaqdataIds.length - 1) {
newselectedfaqdataIds = newselectedfaqdataIds.concat(selectedfaqdataIds.slice(0, -1));
} else if (selectedIndex > 0) {
newselectedfaqdataIds = newselectedfaqdataIds.concat(
selectedfaqdataIds.slice(0, selectedIndex),
selectedfaqdataIds.slice(selectedIndex + 1)
);
}
setSelectedfaqdataIds(newselectedfaqdataIds);
};
{faqdatas.map((faqdata) => (
<ListItem style={{ cursor: 'pointer' }}>
<ListItemIcon>
{selectedfaqdataIds.indexOf(faqdata.id) !== -1}? <AddIcon /> : <RemoveIcon />}
</ListItemIcon>
<ListItemText primary={faqdata.Question} onClick={(event) => handleSelect(event, faqdata.id)} />
</ListItem>
))}

multiple iteration is done for creating the generic filters

I am trying to develop a generic filter component which can have many fields to filter on like color,
size, price range etc and each field might have different types of elements like color may have
checkboxes, radio button and price range might have input element, dropdown etc. To support such varied
cases, I tried to go with this pattern but here I have to iterate the same things multiple times.
I am not sure of this data structure. If anyone has suggestion please help me to improve this code but
the main problem here is "multiple iteration". How can i improve this code?
const filterParams = {
field: {
id : 1, label : 'Field', content: <FieldFilter />
},
employee: {
id : 1, label : 'Employee', content: <Employee />
}
}
<Filter filterParams={filterParams} activeFilterParam="field" />
const Filter = ({ filterParams, activeFilterParam }) => {
const [show, setShow]=useState(false)
return (
<>
<Button secondary icon={filter} onClick={() => setShow(!show)}>Filter</Button>
{show && (
<Card style={{ marginTop: 10 }}>
<Card.Content>
<Tabs activeTab={activeFilterParam}>
<Tabs.List
render={() => {
return (
Object.keys(filterParams).map(filterParam => {
return (
<Tabs.Tab key={filterParam} id={filterParam}>{filterParams[filterParam].label}</Tabs.Tab>
)
}))
}} />
<Tabs.Panels>
{Object.keys(filterParams).map(filterParam => {
return (
<Tabs.Panel key={filterParam} panelId={filterParam}>{filterParams[filterParam].content}</Tabs.Panel>
)
})}
</Tabs.Panels>
</Tabs>
</Card.Content>
<Card.Footer>
<Button>
<Button.Content style={{ marginRight: 10 }}>Save</Button.Content>
<Button.Content secondary onClick={()=>setShow(!show)}>Cancel</Button.Content>
</Button>
</Card.Footer>
</Card>
)}
</>
)
}
If you're not liking the multiple calls to Object.keys(filterParams).map, you could move the loop to the top of the component function. Something like the below might work:
const Filter = ({ filterParams, activeFilterParam }) => {
const [show, setShow]=useState(false)
const {tabs, panels} = Object.keys(filterParams)
.reduce((acc, filterParam) => {
acc.tabs.push(
<Tabs.Tab key={filterParam} id={filterParam}>{filterParams[filterParam].label}</Tabs.Tab>
);
acc.panels.push(
<Tabs.Panel key={filterParam} panelId={filterParam}>{filterParams[filterParam].content}</Tabs.Panel>
);
return acc;
}, { tabs: [], panels: [] });
return (
...
<Card style={{ marginTop: 10 }}>
<Card.Content>
<Tabs activeTab={activeFilterParam}>
<Tabs.List render={() => tabs} />
<Tabs.Panels>
{panels}
</Tabs.Panels>
</Tabs>
...
</Card>
...
)
}
Note that I haven't run this - it likely won't be quite right, but should give the general idea...

Categories

Resources