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;
Related
I have a view that can be called both for creating new entry and updating an existing one.
When Im creating a brend new post, eveything works as expected.
When editing an existing post, the select box for regions is filled AND the current region is selected.
However, for the cities, the select box contains all the cities that belong to the region. But at the same time, the current city is not selected.
Both in the console and in the html-span I can see that the id is correct. But inside the option nothing happens.
EDIT
Full code that works for testing.
In package.json:
...
"react": "18.0.0",
"react-dom": "18.0.0",
"react-router-dom": "6.3.0",
"react-scripts": "4.0.0"
In App.js:
<Route path="test" element={<Test />} />
In Test.js
import React, {useState, useEffect} from "react";
export const Test = () => {
const [name, setName] = useState('');
const [city, setCity] = useState('');
const [region, setRegion] = useState('');
const [regions, setRegions] = useState([]);
const [cities, setCities] = useState([]);
const [fetchingCities, setFetchingCities] = useState(false);
const getCities = async () => {
return [
{_id: 1, region: 'region1', cities: [{id: '6293609d86af877f09bb9bc1', name: 'city1'},{id: '6293609d86af877f09bb9bc2', name: 'city2'}]}
,{_id: 2, region: 'region2', cities: [{id: '6293609d86af877f09bb9bc3', name: 'city3'},{id: '6293609d86af877f09bb9bc4', name: 'city4'}]}
]
}
const getAllCities = async () => {
const all = await getCities();
setRegions(all);
const form = {
region: 1,
city: '6293609d86af877f09bb9bc1',
name: 'testname'
};
if(form){
setName(form.name);
const cts = all.filter(e => e._id === Number(form.region));
if(cts && cts.length && cts[0].cities){
setCities(cts[0].cities);
setRegion(form.region);
setCity(form.city);
console.log('form.city',form.city);
}
}
}
const citySelected = (value) => {
console.log("value",value);
setCity(value);
}
const cityChanged = (value) => {
console.log("cityChanged",value);
const cts = regions.filter(e => e._id === Number(value));
console.log("cts",cts);
if(cts && cts.length && cts[0].cities){
setCities(cts[0].cities);
setRegion(value);
}
}
useEffect(() => {
if(!fetchingCities){
setFetchingCities(true);
getAllCities();
}
}, []);
return (
<>
<div>
<div>
<input type="text" defaultValue={name} onChange={e => setName(e.target.value)}/>
</div>
<div>
{
regions && regions.length > 0 &&
<select defaultValue={region} onChange={e => cityChanged(e.target.value)}>
<option value="">
Select
</option>
{regions.map((region, index) => (
<option key={index} value={region._id}>
{region.region}
</option>
))}
</select>
}
</div>
<div>
<select defaultValue={city} onChange={e => citySelected(e.target.value)}>
<option value="">
Select
</option>
{cities && cities.length > 0 && cities.map((city, index) => (
<option key={index} value={city.id}>
{city.name}
</option>
))}
</select>
</div>
<span>city:{city}</span>
</div>
</>
);
};
You can add a useEffect which sets the value of city every time cities changes.
useEffect(() => {
setCity(cities[0]?.id)
//setCity(undefined) or this if you don't want a city to be preselected.
}, [cities])
You can also add a selected option to verify make sure the selected city is the one shown in the dropdown.
{cities && cities.length > 0 && (
cities.map((cityOption: any, index: any) => (
<option
selected={cityOption.id === city}
key={index}
value={cityOption.id}
>
{cityOption.name}
</option>
))
)}
Turns out the solution is to add the "value" attribute:
<select onChange={e => citySelected(e.target.value)} value={city}>
I know, this topic has been handeled a lot, but I am still lost in my particular example. I have a react-select component, which is a part of another component, which is a part of App component.
SubjectSelect.tsx
export default function SubjectSelect({handleChange, value}) {
return (
<>
<Select
placeholder="Choose subject"
value={value} // set selected value
onChange={handleChange} // assign onChange function
/>
<div><b>Your choice: </b> {value} </div>
</>
)
}
FormStepOne.tsx
import SubjectSelect from "../components/SubjectSelect";
export const SubjectSelector = ({value, handleChange}) => {
return (
<>
<h1 className="text-3xl font-bold underline">
Hi there! Please select book subject
</h1>
<SubjectSelect value={value} onChange={handleChange}/>
</>
);
}
And App.tsx
import React, { useState } from 'react'
import { SubjectSelector } from '../formSteps/stepOne'
import { ShowcaseBooks } from '../formSteps/stepTwo'
const options = [
{ value: 'fiction', label: 'Fiction' },
{ value: 'science', label: 'Science' },
]
export default function App() {
const [books, setBooks] = useState('')
const [selectedValue, setSelectedValue] = useState('');
const handleChange = e => {
setSelectedValue(e.value);
alert('huhu')
}
const value = options.find(obj => obj.value === selectedValue)
return (
<div className="bg-blue-200">
<div className="container mx-auto py-36 bg-blue-200">
<div className="mt-12 px-96">
<SubjectSelector
options={options}
value={value}
onChange={handleChange}/>
<ShowcaseBooks books={books}/>
</div>
</div>
</div>
)
}
Somehow I am not passing the props right, so that my components showcase several errors. App.tsx complains about options and onChange, but I am lost and don't reallyfully undertand what is wrong and how to pass props corretly, so my App.js and therefore Showcase books "know" the selectdValue.
The problem is you pass the handleChange function to SubjectSelector on the onChange prop, while that component is expecting it on a prop named handleChange.
In App.tsx you need something like this
<SubjectSelector
options={options}
value={value}
handleChange={handleChange}/> // This is the fix
Because when you do this
export const SubjectSelector = ({value, handleChange}) =>
You're telling the component the name of the props to expect. You need to change your Subject selector in a similar manner.
For first loading, the prop value you send is empty (because change event is not triggered). so It will turn your app into error.
So you need to check it not empty before parse it to HTML
SubjectSelect.tsx
export default function SubjectSelect({ options, handleChange, selectedVal }) {
return (
<>
<select
value={selectedVal && selectedVal.value} // set selected value
onChange={handleChange} // assign onChange function
>
<option>Choose subject</option>
{options.map((item: any) => {
return (
<option key={item.value} value={item.value}>
{item.label}
</option>
);
})}
</select>
<div>
<b>Your choice: </b> {selectedVal && selectedVal.value}
</div>
</>
);
}
FormStepOne.tsx
import SubjectSelect from "./SubjectSelect";
const SubjectSelector = ({ selectedVal, handleChange, options }) => {
return (
<>
<h1 className="text-3xl font-bold underline">
Hi there! Please select book subject
</h1>
<SubjectSelect
options={options}
selectedVal={selectedVal}
handleChange={handleChange}
/>
</>
);
};
export default SubjectSelector;
And do not use value as variable name or prop to avoid conflict with key of object.
Getting value from change event should be e.target.value
import React, { useState } from "react";
import SubjectSelector from "./SubjectSelect";
const options = [
{ value: "fiction", label: "Fiction" },
{ value: "science", label: "Science" }
];
export default function App() {
const [selectedValue, setSelectedValue] = useState('');
const handleChange = (e: any) => {
setSelectedValue(e.target.value);
alert(e.target.value);
};
const selectVal = options.filter((obj) => obj.value === selectedValue)[0];
return (
<div className="bg-blue-200">
<div className="container mx-auto py-36 bg-blue-200">
<div className="mt-12 px-96">
<SubjectSelector
options={options}
selectedVal={selectVal}
handleChange={handleChange}
/>
</div>
</div>
</div>
);
}
You can check my complete answer here. I hope it might helps you
https://codesandbox.io/s/delicate-smoke-n0eyjc?file=/src/App.tsx:0-804
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>
When I make a selection from the dropdown I saved the selected value to type then when I click the button I add an object to drums, I map over thee drums and based on the type I want to render the component with the same name.
Sandbox here
import React, { useState } from "react";
import uuid from "react-uuid";
import "./styles.css";
const Snare = () => {
return <div>Snare</div>;
};
const Gong = () => {
return <div>Gong</div>;
};
export default function App() {
const [drums, setDrums] = useState([]);
const [type, setType] = useState();
return (
<div className="App">
{drums.map((Drum, index) => (
<Drum.type /> // Why cant I use `.type`?
))}
<label>
Drum type to add:
<select onChange={e => setType(e.target.value)} value={type}>
<option value="">Select...</option>
<option value="Snare">Snare</option>
<option value="Gong">Gong</option>
</select>
<button
onClick={() => {
setDrums([...drums,
{
id: uuid(),
type
}
]);
}}
>
Add drum
</button>
</label>
</div>
);
}
In your case Drum.type is not a component but a string, you need to maintain a map of the string to component and then render it
const map = {
Snare: Snare,
Gong: Gong
};
export default function App() {
const [drums, setDrums] = useState([]);
const [type, setType] = useState();
return (
<div className="App">
{drums.map((Drum, index) => {
const Component = map[Drum.type];
return <Component key={index}/>;
})}
<label>
Drum type to add:
<select onChange={e => setType(e.target.value)} value={type}>
<option value="">Select...</option>
<option value="Snare">Snare</option>
<option value="Gong">Gong</option>
</select>
<button
onClick={() => {
setDrums([
...drums,
{
id: uuid(),
type
}
]);
}}
>
Add drum
</button>
</label>
</div>
);
}
Working demo
That's because the type is a string.
You could create a mapping to solve this and use React.createElement().
Something like:
const mapping = {
'Snare': Snare,
'Gong': Gong
}
{ drums.map(({ type }, index) => (
React.createElement(mapping[type], { key: index })
))
}
I have a hook like this :
export default function AddSections ({ onCheckItem }) {
const checkItem = (item) => {
console.log('check')
}
return (
<div>
<Checkbox
className="section_item"
key={index}
id={section.name}
name="add-sections"
type="radio"
label={'section.label'}
value={'section.name'}
onChange={val => checkItem(item)}
/>
</div>
)
}
for the first time when i check the checkbox the function return the console.log, when i try to undo the check it never works
Problem is you have defined the Checkbox type to be radio and you are using the uncontrolled input so it doesn't allow your to toggle.
You have two solutions
Change the type to checkbox
sample code:
export default function AddSections ({ onCheckItem }) {
const checkItem = (item) => {
console.log('check')
}
return (
<div>
<Checkbox
className="section_item"
key={index}
id={section.name}
name="add-sections"
type="checkbox"
label={'section.label'}
value={'section.name'}
onChange={val => checkItem(item)}
/>
</div>
)
}
Use Controlled input
sample code
export default function AddSections ({ onCheckItem }) {
const [checked, setChecked] = useState('');
const checkItem = (item) => {
setChecked(checked => (checked == item? '': item));
}
return (
<div>
<Checkbox
className="section_item"
key={index}
id={section.name}
name="add-sections"
checked={checked === item}
type="radio"
label={'section.label'}
value={'section.name'}
onChange={val => checkItem(item)}
/>
</div>
)
}
I suppose that Checkbox component is gonna map the props to native checkbox element. If that is the case, you should use checked prop as well. I don't see any hooks in your code though.
import React, {useState, useEffect} from 'react'
export default function AddSections ({ onCheckItem }) {
const [checked, setChecked] = useState(false)
const handleCheckItem = (item) => {
setChecked(!checked)
}
useEffect(() => {
console.log('clicked')
}, [checked])
return (
<div>
<Checkbox
className="section_item"
key={index}
id={section.name}
name="add-sections"
type="radio"
label={'section.label'}
value={'section.name'}
checked={checked}
onChange={handleCheckItem}
/>
</div>
)
}