React dropdown / select not updating state - javascript

Updating state works if the state i am trying to update is outside the users array. But since i am having multiple users i need the state to be inside the objects and update anyway
I keep getting the error TypeError: Cannot read property 'name' of undefined
I've thought of setting state inside of a loop but i was told thats a bad idea.
So [e.target.name]: e.target.value was the only code i could find for dropdowns.
I tried passing id for each of the users but didnt know how to change state using that or what condition to put.
import React, { Component } from 'react'
export default class App extends Component {
state = {
users: [
{
id: uuid(),
firstName: 'John',
lastName: 'Doe',
favColor: 'None'
},
{
id: uuid(),
firstName: 'Jane',
lastName: 'Doe',
favColor: 'None'
}
]
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
render() {
return (
<div>
{this.state.users.map((user) => {
return <div key={user.id}>
<h1>{user.firstName}</h1>
<h1>{user.lastName}</h1>
<form>
<select
name="favColor"
value={user.favColor}
onChange={() => this.handleChange(user.id)}
>
<option value="None" disabled>None</option>
<option value="Blue">Blue</option>
<option value="Red">Red</option>
<option value="Green">Green</option>
</select>
</form>
<h1>Fav Color: {user.favColor}</h1>
<hr />
</div>
})}
</div>
)
}
}
I expect the dropdowns to change state separately for each of the users

Your handleChange method is not accepting the correct arguments.
If you wish to update one user item in array you will need to create a new updated copy of the array and save back into state
handleChange = (e,id) => {
const updatedUser = {...this.state.users.find(x=>x.id ===id), favColor: e.target.value}
this.setState({
users: [...this.state.users.filter(x==>x.id!==id),updatedUser]
})
}
...
onChange={(e) => this.handleChange(e,user.id)}
To simplify mutations of state I can recommend taking a look at Immer
And as #JosephD rightly pointed out this won't mantain order so you will need to do something like this.state.users.map(u => u.id === id ? { ...u, favColor: e.target.value } : u)
Here is a codesandbox based on your code:
https://codesandbox.io/s/loving-cohen-do56l?fontsize=14

<select
name="favColor"
value={this.state.favColor}
onChange={(e) => this.handleChange(e)}> // first Change
handleChange = (e) => {
this.setState({
favColor: e.target.value
})
} // Second Change
This will work for you

You are updating the state the wrong way;
Your state:
users: [
{ id: 1, ... },
{ id: 1, ... }
]
Your update / intention:
users: [
{ id: 1, ... },
{ id: 1, ... }
]
favColor: color // intention, because you don’t pass event
To update the right way, you need to:
pass the event and currentId, to handleChange, of the selected dropdown. Otherwise you cannot know the current user. Also, in your example you don’t pass the event, so you cannot retrieve the information of the dropdown. Causing the name of undefined error.
check when id of user matches with id of dropdown and change the value.
This example should work for you.
https://codesandbox.io/s/small-sea-mw08n

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.

React form getting reset with every change in state?

I am developing a simple crud form with formik in react for Learning purposes, this
Now in this form I am facing two issues
Ist Issue
As Quantity field should only input numbers so I added a onChange listener and updated state if only number are inputted, it works fine, but problem is it reset's the whole form
<Field
className="form-control"
type="text"
name="quantity"
onChange={this.onChangeCheckNumber}
>
</Field>
onChangeCheckNumber = (event) => {
const re = /^[0-9\b]+$/;
// if value is not blank, then test the regex
if (event.target.value === '' || re.test(event.target.value)) {
this.setState({ quantity: event.target.value })
}
}
I think this piece of code is culprit
this.setState({ quantity: event.target.value })
As I am setting state, so I think my whole form gets re rendered again,
How to achieve this functionality perfectly ?
2nd Issue
Warehouse selection depends upon city selection, so I added a onChange on city and called warehouse for selected cityId and I am receiving data, but city drop down gets blocked.
My state properties
state = {
quantity: 0,
price: "0",
comments: '',
itemType: {
key: 0,
value: 'Select'
},
cities: {
key: 0,
value: 'Select'
},
warehouses: {
key: 0,
value: 'Select'
},
sellers: {
key: 0,
value: 'Select'
},
warehouseOptions: null
}
In componentDidMount, I am initializing the dropdown values
componentDidMount() {
this.itemTypeObject();
this.cityObject();
this.sellerObj();
}
Fetching data with axios from server and updating state
cityObject = () => {
axios.get('/cities')
.then(response => {
this.setState({
cities: response.data
});
}).catch(error => {
console.log(error);
});
}
Inside my Formik form tag my city and warehouse dropdowns
<div>
<label>City</label>
<div>
<Field
as="select"
onChange={this.onCityDropdownSelected}
name="city"
>
{this.createCity()}
</Field>
</div>
</div>
<div>
<label>Warehouse</label>
<div>
<Field
className="form-control"
as="select"
name="warehouse"
>
{this.state.warehouseOptions}
</Field>
</div>
</div>
And my onCityDropdownSelected
onCityDropdownSelected = (event) => {
if (event.target.value !== "0") {
this.warehouseObj(event.target.value);
} else {
this.setState({
warehouseOptions: null
})
}
}
warehouseObj = (cityId) => {
axios.get(`/warehouses/cityId/${cityId}`)
.then(response => {
this.setState({
warehouses: response.data
});
this.createWarehouse();
}).catch(error => {
console.log(error);
});
}
After this I get updated values in warehouse drop down, but city drop down opens , but does not change on selection.
In your 1st issue you are changing the whole state by doing this.setState({ quantity: event.target.value }) as it will change your state and clear out all other state values and final state will only contain quantity resulting reseting form state.
To solve that issue you have to de structure your old state and than just change the quantity like this:
this.setState{...state, quantity: event.target.value}
as for the onCityDropdownSelected same issue is occurring. Please do tell me if this answer helps solve your problem.

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'));

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