React Select Unable to Remove Items From State Array - javascript

I am using React Select (https://react-select.com/home) which contains a bunch of options and the onChange handler will identify the selected items and push it into a State array.
The issue im running into is how to remove unselected items from the State array:
import Select from 'react-select'
const [selectedTeams, setSelectedTeams] = useState([]);
function manageSelectedTeams(event) {
console.log('function triggered')
setSelectedTeams([])
event.forEach((team) => {
console.log(team.value)
setSelectedTeams(() => [...selectedTeams, team.value])
})
}
data = [
{
label: "Example"
value: "example"
},
{
label: "Example"
value: "example"
},
{
label: "Example"
value: "example"
},
]
return (
<Select isMulti options={data} onChange={(e) => manageSelectedTeams(e)} closeMenuOnSelect={false} placeholder={"Teams will auto populate here once Location is defined above"} />
)
How would I tackle removing items from selectedTeams when a particular option is unselected/unticked
TIA

react-select handles the insert and delete for you so do not need to create a separate logic for it. When we use onChange the array of selected objects could be one possible parameter. You can refer to the same in the documentation. https://react-select.com/props
Thus you need to update your code as follows:
function manageSelectedTeams(data) {
let finalValueList = data.map((item) => item.value);
console.log(finalValueList);
setSelectedTeams(finalValueList);
}
return (
<Select
isMulti
options={data}
onChange={manageSelectedTeams} //UPDATED
closeMenuOnSelect={false}
placeholder={
"Teams will auto populate here once Location is defined above"
}
/>
);

Related

How to display the data related when an option is clicked on drop down?

I have a drop-down list which is coming from the query and when I click on the option the related data should display. I have this drop-down as shown in image
.
How to display the option only once.
I have this below code:
class StoreLocator extends PureComponent {
constructor(props) {
super(props)
this.state = {
options : [],
}
}
getStoreLocatorDropdown(){
return(
<div>
<select>
<option value="" hidden>Select product/service type</option>
{
this.state.options.map((obj) => {
return <option value={obj.id} changeOption={this.handleChange}>{obj.name}</option>
})
}
</select>
</div>
)
}
handleChange(){
console.log("clicked")
}
async componentDidMount(){
let storeLocatorQuery = StoreLocatorInstance.getStoreLocator()
await fetchQuery(storeLocatorQuery).then((data) => {
this.setState({
options : data.storeLocatorLocations.items
})
this.getStoreLocatorDropdown()
},
(error) => console.log(error)
)
}
render() {
return (
<div>
<h1>Store Locator</h1>
<div>
{this.getStoreLocatorDropdown()}
</div>
</div>
)
}
}
export default StoreLocator
How to display option only once when it's values are repeated. And how to make it clickable and display its related data
To stop duplicate values from being displayed on your options list you can add an additional array(duplicateCheck) which would make sure that the values are not repeating
in your options list:
let duplicateCheck=[];
this.state.options.map((obj) => {
if(duplicateCheck.includes(obj.name))
{
return (null);
}
else
{
duplicateCheck.push(obj.name);
return <option value={obj.id} changeOption={this.handleChange}>{obj.name}</option>}
}
})
Seems like what you are trying to do is to only show the unique/distinct options in the drop down list.
One of the manual way you can resolve this is to filter your options datasource first.
In your case, it is the "this.state.options"
Inside your "componentDidMount" function, you can filter it before setting the value into your state:
var data = [
{ id: 1, name: 'Partsandservices' },
{ id: 2, name: 'Partsandservices' },
{ id: 3, name: 'Petromin' }
];
data.map(item => item.name)
.filter((value, index, self) => self.indexOf(value) === index)
// this should return you ["Partsandservices", "Petromin"]
However, this is not a recommended approach, as the root cause of this duplication should be resolved from the deepest level, which is from the "StoreLocatorInstance.getStoreLocator()".
Since the options returned are repeated name on "Partsandservices", does it contains different meaning?
Maybe Partsandservices in Location A and Partsandservices in Location B?
Or was it a mistake for returning two same names to your application?
You should check on that.

Passing value to state using react-select

I'm new to react and trying to learn on my own. I started using react-select to create a dropdown on a form and now I'm trying to pass the value of the option selected. My state looks like this.
this.state = {
part_id: "",
failure: ""
};
Then in my render
const {
part_id,
failure
} = this.state;
My form looks has 2 fields
<FormGroup>
<Label for="failure">Failure</Label>
<Input
type="text"
name="failure"
placeholder="Failure"
value={failure}
onChange={this.changeHandler}
required
/>
</FormGroup>
<FormGroup>
<Label for="part_id">Part</Label>
<Select
name="part_id"
value={part_id}
onChange={this.changeHandler}
options={option}
/>
</FormGroup>
the changeHandler looks like this
changeHandler = e => {
this.setState({ [e.target.name]: e.target.value });
};
The change handler works fine for the input but the Select throws error saying cannot read property name. I went through the API docs and came up with something like this for the Select onChange
onChange={part_id => this.setState({ part_id })}
which sets the part_id as a label, value pair. Is there a way to get just the value? and also how would I implement the same with multiselect?
The return of react-select onChange event and the value props both have the type as below
event / value:
null | {value: string, label: string} | Array<{value: string, label: string}>
So what the error means is that you can't find an attribute of null (not selected), or any attributes naming as name (you need value or label)
For multiple selections, it returns the sub-list of options.
You can find the related info in their document
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
Update
For your situation (single selection)
option having type as above
const option = [
{value: '1', label: 'name1'},
{value: '2', label: 'name2'}
]
state save selected value as id
changeHandler = e => {
this.setState({ part_id: e ? e.value : '' });
};
pick selected option item via saved id
<Select
name="part_id"
value={option.find(item => item.value === part_id)}
onChange={this.changeHandler}
options={option}
/>
For multiple selections
state save as id array
changeHandler = e => {
this.setState({ part_id: e ? e.map(x => x.value) : [] });
};
pick via filter
<Select
isMulti // Add this props with value true
name="part_id"
value={option.filter(item => part_id.includes(item.value))}
onChange={this.changeHandler}
options={option}
/>
onChange function is a bit different in react-select
It passes array of selected values, you may get first one like
onChange={([selected]) => {
// React Select return object instead of value for selection
// return { value: selected };
setValue(selected)
}}
I have tried the above solutions but some of these solutions does update the state but it doesn't gets rendered on the Select value instantly.
Herewith a demo example:
this.state = {
part_id: null,
};
handleUpdate = (part_id) => {
this.setState({ part_id: part_id.value }, () =>
console.log(`Option selected:`, this.state.part_id)
);
};
const priceOptions = [
{ value: '999', label: 'Item One' },
{ value: '32.5', label: 'Item Two' },
{ value: '478', label: 'Item Three' }
]
<Select
onChange={this.handleUpdate}
value={priceOptions.find(item => item.value === part_id)}
options={priceOptions}
placeholder={<div>Select option</div>}
/>

React DOM - Form with multiple <select>, remove option from another <select> after choose one option

I have a form with 4 inputs consuming data from the same state. What I want is, after a selection, the choose option will be removed from the another 3 selects.
I've tried many things, is difficult to list here, but in a nutshell: I create another list of selected options, filter the players array with this another list, map this and etc..
The problem with this approach is: i don't want to remove the selected item from the input where it was selected. So, i try to create copy of the same original list to each input. But, i think this way too much 'work around'.
Here is my component:
import React, { Component } from 'react';
export default class MatchPlayerSelect extends Component {
state = {
selectedPlayers: [],
players: [
{
name:"edmiel",
matches:0,
wins:0
},
{
name:"willian",
matches:0,
wins:0
},
{
name:"gustavo",
matches:0,
wins:0
},
{
name:"gabriel",
matches:0,
wins:0
}
]
}
render() {
const {players, selectedPlayers} = this.state;
return (
<div className="match-player">
<select onChange={this.setPlayer.bind(this)}>
<option defaultValue value="">-</option>
{
// i need to filter list here
// but this code only list the items on state
players.map((player, index) => {
return(
<option key={index} value={player}>
{player.name}
</option>
)
})
}
</select>
</div>
);
}
setPlayer(sender) {
this.setState({ selectedPlayer: [...this.state.selectedPlayers,
sender.target.value] });
}
}
I need to all select component render each state player (already doing this in the code above). But, when selected, i need to remove this selected option from the another inputs without remove from the focus select input. If exist a better solution to do this in React, please let me know how.
I need to do this using react jsx tools or vanilla js. No jquery!
That's all.
So for first, you should remove this.selectPlayer.bind(this) from the render method, this is not good for React, for more please follow this link.
In the current situation, you need to add one extra array and totally it should look like:
1. players - there you should keep all players(this one serves for filtering only).
2. filteredPlayers - there you should keep the filtered players(this one servers for render the options list).
3. selectedPlayersIds- sure, that the array where you collect all selected player's ids.
Every time when selecting a player, you'll take players array and removing all selected players + new selected player and assign to filteredPlayers array.
Code Snippet(selectPlayer function)
const selectedPlayersIds= [...this.state.selectedPlayersIds, newPlayer.id];
this.setState({
filteredPlayers: this.state.players.filter(player => this.selectedPlayerIds.indexOf(player.id) !== -1),
selectedPlayersIds
})
Use #Array.filter and #Array.includes, moreover, you need to apply a few fixes to your code to make it work:
At setPlayer change the key to selectedPlayers instead selectedPlayer
On mapping the players array to option the value property needs to be player.name and not player
players
.filter(player => !selectedPlayers.includes(player.name))
.map((player, index) => (
<option key={index} value={player.name}>
{player.name}
</option>
));
Full example:
class MatchPlayerSelect extends Component {
state = {
selectedPlayers: [],
players: [
{
name: 'edmiel',
matches: 0,
wins: 0
},
{
name: 'willian',
matches: 0,
wins: 0
},
{
name: 'gustavo',
matches: 0,
wins: 0
},
{
name: 'gabriel',
matches: 0,
wins: 0
}
]
};
render() {
const { players, selectedPlayers } = this.state;
return (
<div className="match-player">
<select onChange={this.setPlayer}>
<option defaultValue value="">
-
</option>
{players
.filter(player => !selectedPlayers.includes(player.name))
.map((player, index) => (
<option key={index} value={player.name}>
{player.name}
</option>
))}
</select>
<div>{JSON.stringify(selectedPlayers)}</div>
</div>
);
}
setPlayer = sender => {
const { selectedPlayers } = this.state;
this.setState({
selectedPlayers: [...selectedPlayers, sender.target.value]
});
};
}
ReactDOM.render(<MatchPlayerSelect />, document.getElementById('root'));

Array not seeing the new update

i have an array of json object which contains title and array of subtitles and i have a select option where am storing the title lists and i made a loop on the subtitles array so i can add inputs depending on the subtitles length the problem is that when i select first item it works fine but when i select the second item from the dropdown list it doesn't see the new update of the array so it shows me an error of undefined because the state didn't update correctly i didn't know how to solve it
here is my array :
constructor(props) {
super(props);
this.state = {
Modules_SubModules_Array: [{
"Module": "",
"SubModules": []
}],
};
}
and then there is the function that i execute when i select item from dropdown list :
Affect_Module_Submodule = (currentModuleTitle, index) => {
if (
this.state.Modules_SubModules_Array.findIndex(
item => item.Module == currentModuleTitle
) < 0
) {
this.state.Modules_SubModules_Array.splice(index, 1, {
Module: currentModuleTitle,
SubModules: []
});
this.setState({
Modules_SubModules_Array: this.state.Modules_SubModules_Array
});
}
};
and there is the loop that i use :
values.Modules_SubModules_Array[this.state.selectedIndex].SubModules.map(
(subModule, index2) => {
return (
<div key={index2}>
<TextFields
style={{ marginLeft: "15%" }}
defaultValue={subModule}
hintText="SubModule Title"
floatingLabelText="SubModule Title"
onChange={e =>
this.props.handleSubModuleChange(
e,
index2,
this.state.selectedIndex,
this.state.result,
values.subModuleTitle
)
}
/>
<input
type="button"
value="remove"
onClick={() =>
this.props.removeSubModule(index2, this.state.result)
}
/>
<br />
<br />
</div>
);
}
);
the selectIndex is the index of the json object which contains the title that i selected from the dropdown list
so when i select a first item from the select the loop works and when i try to change to a second item from the select it won't work it tells me cannot read SubModules of undefined because in my console it prints undefined then it prints the new state so of course it won't work because he sees undefined at first didn't know how to solve it
Array#splice mutates the array and since you're applying it to your state, you mutate it directly. You need to make a copy of state first, apply the changes to the copy and then set it on the state:
Affect_Module_Submodule = (currentModuleTitle, index) => {
let copyArray = this.state.Modules_SubModules_Array.concat();
if (copyArray.findIndex(item => item.Module == currentModuleTitle) < 0) {
copyArray.splice(index, 1, {
Module: currentModuleTitle,
SubModules: []
});
this.setState({
Modules_SubModules_Array: copyArray
});
}
};

React filter best practices

How to not lose React state in this case when I do filter?
When I do filter I lose my previous state and program work not correct, for example, if I choose category sport, then try category fashion, I can't see anything in fashion, the case this all was dropped, when I choose sport.
I am new in React I would like to hear the best practices.
FilterCategory(e) {
// Filter
const filter = this.state.items.filter(
(item) => {
return item.category.indexOf(e.target.name) !== -1
}
)
// Update state
this.setState({
items:filter
})
}
Why not use query string to store filters.
Suppose your url is /products and filter selected is say gender male. then you can append
/products?gender=male.
Now in react using libraries like react-router you can access this query params and get the current filter selected and then perform whatever options you want to like call api etc.
If you further select other filters then just append the new filters again to query params like field1=value1&field2=value2&field3=value3...
And again as location props of react will change you will get the new params in the component.
Advantages of this technique.
1) No headache of maintaining state.
Storing filters in state can become complex and clumsy if not done in proper way.
2) No problem if page gets refreshed.
Suppose your user have selected filters and, page gets refreshed, all filters will be lost if saved in state. But if query string is done it will remain intact.
Due to this reasons i think query string is better option then state.
Just store filtered values as another state property.
state = {
items: [],
filteredItems: []
}
When you do filtering always refer to items and override filteredItems
filterItems(e) {
const filtered = this.state.items.filter(
(item) => {
return item.category.indexOf(e.target.name) !== -1
}
)
this.setState({filteredItems: filtered});
}
The problem is you're setting items to the filtered array returned by filter.
You could use another proprety in your state to store the target's item, so you're keeping your items as they are, something like this:
this.state({
items: [...yourItems],
chosenItem: []
})
filterCategory(e) {
let filter = this.state.items.filter((item) => {
item.category.indexOf(e.target.name) !== -1
})
// Update state keeping items intact
this.setState({
chosenItem: filter
})
}
You can just store and update the filter query in state and only return the filtered items in the render function instead of updating state.
class App extends React.Component {
state = {
items: [
{ category: ['fashion'], name: 'Gigi Hadid', id: 1 },
{ category: ['sports'], name: 'Lebron James', id: 2 },
{ category: ['fashion', 'sports'], name: 'Michael Jordan', id: 3 },
{ category: ['sports', 'tech'], name: 'Andre Iguodala', id: 4 },
{ category: ['tech'], name: 'Mark Zuckerberg', id: 5 },
],
filter: '',
}
handleChange = (e) => {
this.setState({ filter: e.target.value });
}
render() {
const { items, filter } = this.state;
let shownItems = items;
if (filter) {
shownItems = items.filter(({ category }) => category.includes(filter));
}
return (
<div>
<select value={filter} onChange={this.handleChange}>
<option value="">All</option>
<option value="fashion">Fashion</option>
<option value="sports">Sports</option>
<option value="tech">Tech</option>
</select>
<div>
<ul>
{shownItems.map(({ name, id }) => <li key={id}>{ name }</li>)}
</ul>
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Categories