I'm with a little issue here right now with Material-UI chips component. I have a select with a list of users. When you select a user, a chip with his number appears right below the form. Everything works just fine but the chips are overwriting, not adding the new ones. Here my code:
import React from 'react';
import Chip from '#material-ui/core/Chip';
export default function Test(){
const [chipData, setChipData] = React.useState([]);
const userArray = [
{key: 1, label: name1},
{kel: 2, label: name2}
];
const [userValue, setUserValue] = React.useState('');
// This come from the select form onChange
const handleSelect = (e) => {
setUserValue(e.target.value);
setChipData([...chipData, {key: e.target.value, label: e.target.value}]);
}
const handleDelete = chipToDelete => () => {
setChipData(chips => chips.filter(chips => chips.key !== chipToDelete.key));
}
return(
<div>
<TextField
select
value={userValue}
onChange={e => handleSelect(e)}
>
{userArray.map(option => (
<MenuItem key={option.key} value={option.label}>
{option.label}
</MenuItem>
))}
</TextField>
{chipData.map(data => {
return(
<Chip
key={data.key}
label={data.label}
onDelete={handleDelete(data)}
/>
);
})}
</div>
);
}
I tried with something like chipData.push({data...}) that works just fine, but do not allow me to add more items if I delete any of them. With the code above the chips are just overwriting the one clicked before and do not add to the array.
Online Example, please validate if you have the issue:
https://codesandbox.io/s/material-ui-n7kbp
only edited key render values:
{chipData.map((data, index) => {
return (
<Chip
key={data.key + index}
label={data.label}
onDelete={handleDelete(data)}
/>
);
})}
Related
Here is my problem: I'm making in Ract a clone of Google Forms. The structure of the data in the Redux store is as follows:
sections: [
{
sectionTitle: 'first section',
questions: [
{
questionType: 'checkbox',
question: 'some question',
options: [
{
number: 1,
text: 'sometimes',
},
],
},
],
},
],
In the feature loading the main edig page, I render this questions in the following map:
{form.sections.map((section, index) => {
return (
<Box key={index}>
<Box mb={5}>
<SectionCard index={index} />
</Box>
{section.questions.map((question, questionIndex) => {
return (
<QuestionCard
key={questionIndex}
sectionIndex={index}
questionIndex={questionIndex}
/>
);
})}
</Box>
);
})}
Each component picks the values from the store with useSelector, like the question description, the options, question type.
QuestionCard:
const QuestionCard = ({ sectionIndex, questionIndex }) => {
const question = useSelector(
state => state.form.sections[sectionIndex].questions[questionIndex]
);
const [hasQuestionChanged, setHasQuestionChanged] = useState(false);
const dispatch = useDispatch();
const handleChangeQuestionValue = e => {
dispatch(
setQuestionDescription({
sectionNumber: sectionIndex,
questionNumber: questionIndex,
newValue: e.target.value,
})
);
setHasQuestionChanged(true);
};
return(
...
<CardContent>
<TextField
multiline
label={`Question ${questionIndex + 1}`}
value={question.question}
margin='normal'
variant='standard'
onChange={handleChangeQuestionValue}
fullWidth
/>
{question.questionType === 'multiple' &&
question.options.map((option, index) => {
return (
<RadioQuestion
key={index}
sectionIndex={sectionIndex}
questionIndex={questionIndex}
optionIndex={index}
/>
);
})}
...
)
RadioQuestion
const RadioQuestion = memo(({ sectionIndex, questionIndex, optionIndex }) => {
const theme = useTheme();
const dispatch = useDispatch();
const [selected, setSelected] = useState(false);
const option = useSelector(
state =>
state.form.sections[sectionIndex].questions[questionIndex].options[
optionIndex
]
);
const handleChangeOptionDescription = e => {
dispatch(
setQuestionOptionDescription({
sectionIndex: sectionIndex,
questionIndex: questionIndex,
optionIndex: optionIndex,
description: e.target.value,
})
);
// Update the state
const handleChangeOptionValue = e => {
setSelected(!selected);
};
return(
...
<FormControl mt='1rem'>
<RadioGroup>
<Box justifyContent='center' sx={{ alignItems: 'center' }}>
<FormControlLabel
labelPlacement='top'
value={option.number}
control={<Radio />}
checked={selected}
sx={{ mt: 3 }}
onClick={handleChangeOptionValue}
/>
<FormControl>
<TextField
fullWidth
label={`Option ${option.number}`}
value={option.text ? option.text : ''}
variant='standard'
margin='normal'
onChange={handleChangeOptionDescription}
/>
</FormControl>
</Box>
</RadioGroup>
</FormControl>
...
)
SectionCard
const SectionCard = ({ index }) => {
const isMobile = useMediaQuery('(max-width: 400px)');
const theme = useTheme();
const dispatch = useDispatch();
const [errorTitle, setErrorTitle] = useState();
const { sectionTitle } = useSelector(state => state.form.sections[index]);
const totalSections = useSelector(
state => state.form.computedSections.computed.totalSections
);
const handleChangeSectionTitle = e => {
dispatch(
changeSectionTitle({ sectionIndex: index, newValue: e.target.value })
);
};
return (
...
<FormControl>
<TextField
required
autoComplete='off'
sx={{ minWidth: isMobile ? 150 : 400 }}
label='Section Title'
variant='standard'
value={sectionTitle}
margin='normal'
color={theme.palette.secondary[100]}
onChange={handleChangeSectionTitle}
error={errorTitle}
/>
{errorTitle && (
<FormHelperText error>
The Section Title can't be empty
</FormHelperText>
)}
</FormControl>
...
)
The problem is that when I edit a question filed and dispatch the value it rerenders all the question cards, which makes typing a bit slow. If I try to memoize the QuestionCard or the SectionCard I get a TypeError:
const SectionCard = memo(({ index }) => {...})
>Uncaught TypeError: Component is not a function
How the component looks
As you can see, every time I change a value, 20 rerenders happen, so I need to only rerender que component that has changed and I can't find the way to do it. I believe that the root problem is that the state is mapping through the sections and questions, and returning a new state, making all the components rerender.
As another guess on the problem and its solution, I understand that I shoul use useSelector to memoize, but I am really lost since the data structured is so nested.
Thanks in advance
I tried memoizing the function that renders the Section and the Question, which gives an error, I tried to useMemo on the parent component ant render the memoized component instead of the original. I am not sure on how to handle to render only when that specific component has changed.
I have stomped on a problem that i don't know how to resolve.
I have a react-select input that passes a selected value to another select input.
When a user clicks on submit button then i have to display an array of every selector input that the user has selected as a list of items and then the form should reset all select values.
For this, i have tried submitting to an array but it only shows one item from all selectors and form doesn't reset its values.
Here is a sandbox link
https://codesandbox.io/s/awesome-carson-i99g8p?file=/src/App.js
How can I archive this I have tried everything but I could not figure out how can i archive this functionality.
Ok. First tricky thing of react-select is, the value you assign to the component must be an object with label and valueproperties, not just the value. Meaning, when handling change events, you should set the state using the full event object.
This is the code mostly fixed:
import React, { useState, useEffect } from "react";
import Select from "react-select";
const options = [
{ value: "0", label: "0" },
{ value: "1", label: "1" },
{ value: "2", label: "2" }
];
const options2 = [
{ value: "Before Due Date", label: "Before Due Date" },
{ value: "After Due Date", label: "After Due Date" }
];
const App = (props) => {
const [numOfDays, setNumOfDays] = useState('');
const [beforeDueDate, setBeforeDueDate] = useState('');
const [items, setItems] = useState([]);
const [reminders, setReminders] = useState([]);
const submit = (event) => {
event.preventDefault(); // <-- prevent form submit action
const obj = {};
obj.id = reminders.length + 1;
obj.numOfDays = numOfDays.value;
obj.beforeDueDate = beforeDueDate.value;
setReminders((items) => [...items, obj]); // <-- update arr state
setBeforeDueDate("");
setNumOfDays("");
};
function numOfDaysHandle(event) {
// const numOfDays = event.value;
setNumOfDays(event);
setItems((items) => [...items, items]);
}
function beforeDueDateHandle(event) {
// const value = event.value;
setBeforeDueDate(event);
}
const removeReminder = (id) => {
setReminders(reminders.filter((item) => item.id !== id));
};
return (
<>
<form>
<div>
{reminders.map((item, index) => (
<div key={index}>
<div>
<span>
{item.numOfDays} days {item.beforeDueDate}
</span>
<button onClick={() => removeReminder(item.id)}>
removeItem
</button>
</div>
</div>
))}
</div>
<div>
<Select
options={options}
value={numOfDays}
id="numOfDays"
placeholder="Days"
isSearchable={false}
//onChange={numOfDaysHandle}
onChange={numOfDaysHandle}
/>
<Select
options={options2}
value={beforeDueDate}
id="beforeDueDate"
placeholder="Before Due Date"
isSearchable={false}
onChange={beforeDueDateHandle}
/>
</div>
{items.map((item, index) => (
<div key={index}>
<Select
options={options}
value={item.numOfDays}
id="numOfDays"
placeholder="Days"
isSearchable={false}
onChange={numOfDaysHandle}
/>
<Select
options={options2}
value={item.beforeDueDate}
id="beforeDueDate"
placeholder="Before Due Date"
isSearchable={false}
onChange={beforeDueDateHandle}
// onClick={() => setItems((items) => [...items, items])}
/>
</div>
))}
<button
onClick={submit}
//disabled={!numOfDays}
>
Set Reminder
</button>
</form>
</>
);
};
export default App;
Please see if you can move forward now, I could not understand what you want exactly with the array of <Select /> elements.
There are 4 select Components with dependant dropdown menu.But when I select an option its not displaying in the input field although my 'selectedPlanets' state is updating just right.
Here is my code -
import React, { useState } from "react";
import "../css/Destination.css";
function Destination(props) {
const [selectedPlanets, SetselectedPlanets] = useState([
null,
null,
null,
null,
]);
const OnSelectPlanet = async (e, key) => {
const clonedSelectedPlanets = JSON.parse(JSON.stringify(selectedPlanets));
clonedSelectedPlanets[key] = e.target.value;
SetselectedPlanets(clonedSelectedPlanets);
};
const CustomSelectComponents = ({ value, options, OnSelect}) => {
return (
<select value={value} onChange={OnSelect}>
<option> -- Select a Planet -- </option>
{options.map((option) => {
return <option key = {option.name} value={option.name} >{option.name}</option>;
})}
</select>
);
};
const OptionsToRender = (Alloptions, AllselectedOptions, index) => {
console.log(AllselectedOptions);
const optionstoRender =
AllselectedOptions[index] != null
? Alloptions.filter(
(option) =>
!AllselectedOptions.some(
(selectedOption) =>
option && selectedOption && option.name === selectedOption
)
)
: Alloptions;
return optionstoRender;
};
return (
<>
<div className="Parent_Card">
{selectedPlanets.map((planet, index) => {
const options = OptionsToRender(props.planets, selectedPlanets, index);
return (
<>
{console.log(index)}
<CustomSelectComponents
value={
selectedPlanets[index] != null ? selectedPlanets[index] : ""
}
options={options}
OnSelect={(e) => OnSelectPlanet(e, index)}
key={index}
/>
</>
);
})}
</div>
</>
);
}
export default Destination;
I tried debugging it and figured that its maybe because of how and when my component is rendering.But I dont know why and hence not able to find the solution.
My expected result is when I am choosing an option it shows in the input field.
Your code could benefit from a few different approaches to building your 4 different select components:
the use of controlled components
(https://reactjs.org/docs/forms.html#controlled-components)
separating the state for each of the different selects
refactoring the <CustomSelectComponent /> to be a component that only accepts props
Here is an example of those approaches in practice that might provide some direction on getting these selects operating as expected!
import React, { useState } from 'react';
import '../css/Destination.css';
// custom select component
const CustomSelectComponent = ({ onChange, options, value }) => (
<select onChange={onChange} value={value}>
<option> -- Select a Planet -- </option>
{options.map(option => (
<option key={option.name} value={option.name}>
{option.name}
</option>
))}
</select>
);
const Destination = () => {
// mock props
const props = { planets: [{ name: 'Pluto' }, { name: 'Earth' }] };
// separated state
const [selectOneValue, setSelectOneValue] = useState('');
const [selectTwoValue, setSelectTwoValue] = useState('');
const [selectThreeValue, setSelectThreeValue] = useState('');
const [selectFourValue, setSelectFourValue] = useState('');
return (
<div className="Parent_Card">
{/* each custom select component is now controlled by it's own state */}
<CustomSelectComponent
onChange={e => setSelectOneValue(e.target.value)}
options={props.planets}
value={selectOneValue}
/>
<CustomSelectComponent
onChange={e => setSelectTwoValue(e.target.value)}
options={props.planets}
value={selectTwoValue}
/>
<CustomSelectComponent
onChange={e => setSelectThreeValue(e.target.value)}
options={props.planets}
value={selectThreeValue}
/>
<CustomSelectComponent
onChange={e => setSelectFourValue(e.target.value)}
options={props.planets}
value={selectFourValue}
/>
</div>
);
};
export default Destination;
//value={items.find(x => x.item === selectedValue)}import { Fragment, useState } from //"react";
import Select from 'react-select';
let items = [
{
item: 1,
name: "tv"
},
{
item: 2,
name: "PC"
}
]
const Home = () => {
const [selectedValue, setSelectedValue] = useState(6)
const handleChange = obj => {
setSelectedValue(obj.item)
}
return (
<Fragment>
<div>Home page</div>
<p>Test React Select...</p>
<Select
value={items.find(x => x.item === selectedValue)}
options={items.map(({item, name}) => ({value: name, label: item}))}
onChange={handleChange}
/>
<p>selected Value:...</p>
{selectedValue}
</Fragment>
)
}
export default Home;
It would be helpful if you could provide more information, like if you're getting an error.
However, I think your problem is twofold:
the value of your Select component is being set to the entire object instead of the value.
You've mixed up the value and label keys in the options prop.
Try this:
<Select
value={items.find(x => x.item === selectedValue).item}
options={items.map(({item, name}) => ({value: item, label: name}))}
onChange={handleChange}
/>
EDIT: You don't need to use find, you already have this value:
<Select
value={selectedValue}
options={items.map(({item, name}) => ({value: item, label: name}))}
onChange={handleChange}
/>
I am using Multi Select box from material-ui v4.
I set a default value in the useState and I can see the values but when I open the select the default list is not selected and when I click on it, it added again to the select box so I have the same value multiple time.
/*
the full list configurationList [{_id: '21375d87eed65e103c790121', configurationName: 'Regime1'},{_id: '21375d87eed65e103c790122', configurationName: 'Regime2'},
{_id: '21375d87eed65e103c790123', configurationName: 'Regime3'}]
The currentHistogramConfigurationList [{_id: '21375d87eed65e103c790121', configurationName: 'Regime1'}]*/
const [
histogrammeConfigurations,
setHistogrammeConfigurations
] = useStateWithDep(
currentHistogramConfigurationList ? currentHistogramConfigurationList : []
);
const handleChange = (event) => {
console.log('*handle_Change', event.target.value);
setHistogrammeConfigurations(event.target.value);
};
const handleDelete = (_id) => () => {
console.log('*handle_Delete', _id);
const chipDataConst = [...histogrammeConfigurations];
const chipToDelete = chipDataConst.map(({ _id }) => _id).indexOf(_id);
console.log('*handle_chipToDelete', chipToDelete);
chipDataConst.splice(chipToDelete, 1);
setHistogrammeConfigurations(chipDataConst);
};
//...
<Select
labelId='demo-mutiple-chip-label'
id='demo-mutiple-chip'
multiple
value={histogrammeConfigurations}
onChange={handleChange}
input={<Input id='select-multiple-chip' />}
renderValue={(selected) => (
<div className={classes.chips}>
{selected.map((value) => {
return (
<Chip
key={value._id}
label={value.configurationName}
className={classes.chip}
onDelete={handleDelete(value._id)}
/>
);
})}
</div>
)}
MenuProps={MenuProps}
>
{configurationList.map((item) => (
<MenuItem
key={item._id}
value={item}
style={getStyles(item._id, histogrammeConfigurations, theme)}
// selected
>
{item.configurationName}
</MenuItem>
))}
</Select>
Try to handle the input like this
const [histogrammeConfigurations, setHistogrammeConfigurations ] = useStateWithDep(currentHistogramConfigurationList ? currentHistogramConfigurationList : []);
<Select
value={histogrammeConfigurations}
input={
<Input
id='select-multiple-chip'
value={histogrammeConfigurations}
/>
}
>
...
</Select>