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
});
}
};
Related
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"
}
/>
);
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.
I'm having trouble updating a list of elements using React, when I run the code below and click on a 'star' element, react updates ALL the elements in this.state.stars instead of just the element at the given index:
class Ratings extends React.Component {
constructor(props) {
super(props);
let starArr = new Array(parseInt(this.props.numStars, 10)).fill({
icon: "*",
selected: false
});
this.state = {
stars: starArr
};
this.selectStar = this.selectStar.bind(this);
}
selectStar(ind) {
this.setState({
stars: this.state.stars.map((star, index) => {
if (index === ind) star.selected = !star.selected;
return star;
})
});
}
makeStars() {
return this.state.stars.map((star, ind) => (
<span
className={star.selected ? "star selected" : "star"}
onClick={() => this.selectStar(ind)}
key={ind}
>
{star.icon}
</span>
));
}
render() {
return (
<div className="star-container">
<span>{this.makeStars()}</span>
</div>
);
}
}
Can anyone point me in the right direction here? Not sure why this is happening!
Your problem is in how you're instantiating your Array:
let starArr = new Array(parseInt(this.props.numStars, 10)).fill({
icon: "*",
selected: false
});
What that line is doing is filling each item in the array with the same object. Not objects all with the same values, but a reference to the same object. Then, since you're mutating that object in your click handler, each item in the Array changes because they're all a reference to that same object.
It's better to do a non-mutating update, like this:
this.setState({
stars: this.state.stars.map((star, index) =>
(index === ind)
? { ...star, selected: !star.selected }
: star
)
});
This will instead create a copy of the object at the Array index except with the selected property toggled.
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'));
In my React state, I want to reorder an array of 3 objects by always putting the selected one in the middle while keeping the others in ascending order.
Right now, I'm using an order property in each object to keep track of the order, but this might not be the best approach.
For example :
this.state = {
selected: 'item1',
items: [
{
id: 'item1',
order: 2
},
{
id: 'item2'
order: 1
},
{
id: 'item3'
order: 3
}
]
}
Resulting array : [item2, item1, item3]
Now, let's imagine that a user selects item2. I will update the selected state property accordingly, but how can I update the items property to end up with a result like this:
this.state = {
selected: 'item2',
items: [
{
id: 'item1',
order: 1
},
{
id: 'item2'
order: 2
},
{
id: 'item3'
order: 3
}
]
}
Resulting array : [item1, item2, item3]
How would you do it? I have seen some lodash utility functions that could help but I would like to accomplish this in vanilla JavaScript.
You could do something crude like this:
// Create a local shallow copy of the state
var items = this.state.items.slice()
// Find the index of the selected item within the current items array.
var selectedItemName = this.state.selected;
function isSelectedItem(element, index, array) {
return element.id === selectedItemName;
};
var selectedIdx = items.findIndex(isSelectedItem);
// Extract that item
var selectedItem = items[selectedIdx];
// Delete the item from the items array
items.splice(selectedIdx, 1);
// Sort the items that are left over
items.sort(function(a, b) {
return a.id < b.id ? -1 : 1;
});
// Insert the selected item back into the array
items.splice(1, 0, selectedItem);
// Set the state to the new array
this.setState({items: items});
This assumes the size of the items array is always 3!
I'm gonna be lazy and just outline the steps you need to take.
Pop the selected item out of the starting array
Push the first item of the starting array into a new array
Push the selected item into the new array
Push the last item of the starting array into the new array
Set your state to use the new array
You can do something like:
NOTE: This works assuming there would three items in the array. However, if there are more we just need to specify the index position in the insert function.
this.state = {
selected: 'item1',
items: [
{
id: 'item1',
order: 1
},
{
id: 'item2',
order: 2
},
{
id: 'item3',
order: 3
}
]
};
// To avoid mutation.
const insert = (list, index, newListItem) => [
...list.slice(0, index), // part of array before index arg
newListItem,
...list.slice(index) // part of array after index arg
];
// Get selected item object.
const selectedValue = value => this.state.items.reduce((res, val) => {
if (val.id === selectedValue) {
res = val;
}
return res;
}, {});
const filtered = this.state.items.filter(i => i.id !== state.selected);
const result = insert(filtered, 1, selectedValue(this.state.selected));
We can get rid of the extra reduce if instead of storing id against selected you store either the index of the item or the whole object.
Of course we need to use this.setState({ items: result }). This solution would also ensure we are not mutating the original state array at any point.
I put together a fully working example what can be extended on so you can experiment with different ways to achieve your intended use-case.
In this case I created a button component and rendered three of them to provide a means of changing the selected state.
Important things to remember, always use the setState() function for updating React Class state. Also, always work on state arrays and objects with a cloned variable as you'll want to update the whole object/array at once. Don't modify attributes of pointer variables pointing to state objects or arrays.
It is very possible to add bugs to your code by referencing state objects/arrays and then changing their properties (accidentally or not) by modifying the pointer referencing the object. You will lose all guarantees on how the state will update, and comparing prevState or nextState with this.state may not work as intended.
/**
* #desc Sub-component that renders a button
* #returns {HTML} Button
*/
class ChangeStateButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = ({
//any needed state here
});
}
handleClick(e) {
//calls parent method with the clicked button element and click state
this.props.click(e.nativeEvent.toElement.id);
}
render() {
return (
<button
id = {this.props.id}
name = {this.props.name}
className = {this.props.className}
onClick = {this.handleClick} >
Reorder to {this.props.id}!
</button>
);
}
}
/**
* #desc Creates button components to control items order in state
* #returns {HTML} Bound buttons
*/
class ReorderArrayExample extends React.Component {
constructor(props) {
super(props);
this.reorderItems = this.reorderItems.bind(this);
this.state = ({
selected: 'item1',
//added to give option of where selected will insert
selectedIndexChoice: 1,
items: [
{
id: 'item1',
order: 2
},
{
id: 'item2',
order: 1
},
{
id: 'item3',
order: 3
}
]
});
}
reorderItems(selected) {
const {items, selectedIndexChoice} = this.state,
selectedObjectIndex = items.findIndex(el => el.id === selected);
let orderedItems = items.filter(el => el.id !== selected);
//You could make a faster reorder algo. This shows a working method.
orderedItems.sort((a,b) => { return a.order - b.order })
.splice(selectedIndexChoice, 0, items[selectedObjectIndex]);
//always update state with setState function.
this.setState({ selected, items: orderedItems });
//logging results to show that this is working
console.log('selected: ', selected);
console.log('Ordered Items: ', JSON.stringify(orderedItems));
}
render() {
//buttons added to show functionality
return (
<div>
<ChangeStateButton
id='item1'
name='state-button-1'
className='state-button'
click={this.reorderItems} />
<ChangeStateButton
id='item2'
name='state-button-2'
className='state-button'
click={this.reorderItems} />
<ChangeStateButton
id='item3'
name='state-button-2'
className='state-button'
click={this.reorderItems} />
</div>
);
}
}
/**
* #desc React Class renders full page. Would have more components in a real app.
* #returns {HTML} full app
*/
class App extends React.Component {
render() {
return (
<div className='pg'>
<ReorderArrayExample />
</div>
);
}
}
/**
* Render App to DOM
*/
/**
* #desc ReactDOM renders app to HTML root node
* #returns {DOM} full page
*/
ReactDOM.render(
<App/>, document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root">
<!-- This div's content will be managed by React. -->
</div>