when using {react-select} Cannot read property 'name' of undefined - reactjs

I am very beginning to reactJS and front end
I added react-select npm for my dropdown like below, before added react-select everything is working fine. How to define name in Select?
<div className="container">
<div className="row">
<div className="col-md-4" />
<div className="col-md-4">
<Select
options={this.state.allGenres}
onChange={this.props.onDataChange}
name="genre"
/>
</div>
<div className="col-md-4" />
</div>
</div>
this is my array,
var Data = response.data;
const map = Data.map((arrElement, index) => ({
label: arrElement,
value: index
}));
example:
[
{
"label": "Action",
"value": 0
},
{
"label": "Comedy",
"value": 1
},
{
"label": "Documentary",
"value": 2
}
]
error message coming in here,
dataChange(ev, action) {
this.setState({
[ev.target.name]: ev.target.value
});
}
render()
render() {
return (
<Movie
onPostData={this.postData.bind(this)}
onDataChange={this.dataChange.bind(this)}
/>
);
}
Error
Uncaught TypeError: Cannot read property 'name' of undefined
at Movies.dataChange

You expect the first argument in react-select´s onChange method to be an event object, but it isn't.
The first argument is the selected option (or options if you have isMulti set).
There is also a second argument which is an object with the following attributes:
action: The action which triggered the change
name: The name given to the Select component using the name prop.
So if you want to use the name:
onDataChange={(value, action) => {
this.setState({
[action.name]: value
})
}}
Reference in source code

I worked around this method and it worked.
handleSelectChange: function(name) {
return function(newValue) {
// perform change on this.state for name and newValue
}.bind(this);
},
render: function() {
return (
<div>
<Select ...attrs... onChange={this.handleSelectChange('first')} />
<Select ...attrs... onChange={this.handleSelectChange('second')} />
</div>);
}

This is how you can get the value(s) of the selected option(s) and the name of the input as well.
For more info, check this issue on Github.
handleChange = (selectedOptions, actionMeta) => {
const inputName = actionMeta.name;
let selectedValues;
if (Array.isArray(selectedOptions)) {
// An array containing values of the selected options (like: ["one", "two", "three"]
selectedValues = selectedOptions.map(option => option.value);
// OR, use this if you want the values of the selected options as a string separated by comma (like: "one,two,three")
selectedValues = selectedOptions.map(option => option.value).join(",");
} else {
// An array containing the selected option (like: ["one"])
selectedValues = [selectedOptions.value];
// OR, use this if you want the value of the selected option as a string (like: "one")
selectedValues = selectedOptions.value;
}
// Do whatever you want with the selected values
//..
this.setState({
[inputName]: selectedValues
});
}
<Select
name="genre"
options={this.state.allGenres}
onChange={this.handleChange}
/>

Related

how to set checkbox in useState and remove duplicate checkbox value in react

I have two problems with the checkbox
1- When I click on each checkbox, they are correctly added to plantsFill, but duplicate items are also saved. I want it to be deleted if the plant_id is duplicated.
2- When I uncheck it, that item is still stored in my plantsFill and is not deleted
const allPlants = [
{
"plant_id": 1,
"title": "title 1",
},
{
"plant_id": 2,
"title": "title 2",
},
{
"plant_id": 3,
"title": "title 3",
},
];
const handleCheckClick = (e) => {
setPlantsFill([...plantsFill, {plant_id: e.target.value}]);
};
{allPlants.map((t) => {
return (
<div key={t.plant_id}>
<label htmlFor="">
<input
type="checkbox"
value={t.plant_id}
onChange={handleCheckClick}
/>
{t.title}
</label>
</div>
);
})}
Your problem is caused by your spreadings, who recreate the same array with a new value without deleting the old one.
If you really want to use spread syntax, you will need to deal with sorting, because the new value will be added last in the array (so even if you filter(), your modified checkbox will end up changing it's position on the list). I recommend you to simply use map() to edit them.
You can add a checked property to each of your checkbox, and set their value using event.target.checked or their own checked property, like:
const [allPlants, setPlantFill] = React.useState([{
"id": 1,
"title": "title 1",
checked: false
}, {
"id": 2,
"title": "title 2",
checked: false
}, {
"id": 3,
"title": "title 3",
checked: false
}]);
const handleCheckClick = (id) => {
setPlantsFill(allPlants.map((checkbox) => checkbox.id === id ? { ...checkbox, checked: !checkbox.checked } : checkbox));
};
return (
{allPlants?.map((checkbox) => (
<div key={checlbox.id}>
<label htmlFor="">
<input
type="checkbox"
value={checkbox.checked}
onChange={() => handleCheckClick(checkbox.id)}
/>
{checkbox.title}
</label>
</div>
)) ?? (
<div>
Loading...
</div>
)}
);
The problem is with handle handleCheckClick function, in your code you are concating items instead of updating their value. One more thing onChange is called for checking and unchecking so you need to check (no pan intended) what is the new status to see if you need to add to the array or remove it from the array.
example:
const handleCheckClick = (e) => {
if (e.checked) {
setPlantsFill(previousPlantsFill => [...previousPlantsFill, {plant_id: e.target.value}]);
return;
}
setPlantsFill(previousPlantsFill => previousPlantsFill.filter(plantFill => plantFill.plant_id !== e.target.value));
};
and a more clean and robust example (checking if value already exists before adding, using useCallback, and deconstruct syntax)
const handleCheckClick = useCallback((e) => {
if (e.checked) {
setPlantsFill(previousPlantsFill => {
if (previousPlantsFill.some(({plant_id}) => plant_id === e.target.value)) {
return previousPlantsFill
}
return [...previousPlantsFill, {plant_id: e.target.value}]
});
return;
}
setPlantsFill(previousPlantsFill => previousPlantsFill.filter(({plant_id}) => plant_id !== e.target.value));
}, []);
Assuming allPlants do not change since you define it as a const and you just want to keep track of what plants are being checked. You can keep a single array of all the plant_id's.
const [plantsFill, setPlantsFill] = useState([]);
const handleCheckClick = (e) => {
const value = e.target.value;
const hasPlant = e.target.checked;
setPlantsFill((prevPlantsFill) => {
// if in the array return the array without the value
if (hasPlant) return prevPlantsFill.filter((plant) => plant !== value);
// else return the old array with the new value
return [...prevPlantsFill, value];
});
};
If you want to get all the plant objects which are checked you can do it like so
const allCheckedPlants = allPlants.filter((plant) =>
plantsFill.includes(plant.plant_id)
);
Hope this helps you with your project!

Display text according to the value of react select

I would like to display a different text according to the value selected in my react-select. handlechange time is working fine, my problem is when I try to retrieve the value in my p tag to display different text depending on the value
My code:
const timeSlots = [
{
value: 0,
label: 'what is your choice ?'
},
{
value: 1,
label: 'choice 1'
},
{
value: 2,
label: 'choice 2'
},
{
value: 3,
label: 'choice 3'
},
{
value: 4,
label: 'choice 4'
}
]
class WidgetBooking extends Component {
state ={
selectedOption: null,
}
handleChangeTime = (selectedOption) => {
this.setState({ selectedOption });
console.log(selectedOption);
}
render() {
const { selectedOption } = this.state;
return (
<>
<div className="time-slots padding-bottom-30px">
<Select
value={selectedOption}
onChange={this.handleChangeTime}
placeholder="what is your choice ?"
options={timeSlots}
/>
</div>
{selectedOption === '1' &&
<p className="d-block widget-title">756, 23 $</p>
}
{selectedOption === '2' &&
<p className="d-block widget-title">865, 23 $</p>
}
{selectedOption === '3' &&
<p className="d-block widget-title">756, 23 $</p>
}
</div>
</>
);
}
}
I would like to display a different price in a p tag, like show above.
Thanks for your help
You've got two issues here:
confusion between an option and the option's value
confusion between string and number
Option vs. Value
What do you see when you call console.log(selectedOption) inside handleChangeTime? I see a complete option object that looks like {value: 2, label: "choice 2"}. That's what you are saving to this.state.selectedOption. But when you compare it in your render, you are expecting this.state.selectedOption to be a numeric string like '1' or '2' -- not an object.
Either one is fine to setState with, but you have to be consistent about what it is or else you will never have a match. If you want to keep handleChangeTime the same, then your render checks need to look like selectedOption?.value === 1. If you want to keep your render() the same, then you need to be setting the state like this.setState({ selectedOption: selectedOption.value.toString() }). That toString() brings us to the next issue.
Number vs. String
When you are comparing values, the number 1 and the string '1' are not the same. If you were dealing with the DOM select element directly, you would likely wind up with string values. But the react-select package calls onChange with the option in the same format that you provided it. In this case, your value property is a number. Again it's consistency that's important. I recommend that you keep it a number everywhere and change your render conditions from selectedOption === '1' to selectedOption === 1.
Corrected Code:
import React, { Component } from "react";
import Select from "react-select";
const timeSlots = [ /* ... */ ];
class WidgetBooking extends Component {
state = {
selectedOption: null // will be a number or null
};
handleChangeTime = ({ value }) => { // destructure to get just the value
this.setState({ selectedOption: value });
};
render() {
const { selectedOption } = this.state;
return (
<>
<div className="time-slots padding-bottom-30px">
<Select
value={selectedOption}
onChange={this.handleChangeTime}
placeholder="what is your choice ?"
options={timeSlots}
/>
</div>
{selectedOption === 1 && (
<p className="d-block widget-title">756, 23 $</p>
)}
{selectedOption === 2 && (
<p className="d-block widget-title">865, 23 $</p>
)}
{selectedOption === 3 && (
<p className="d-block widget-title">756, 23 $</p>
)}
</>
);
}
}

In ReactJS how do I keep track of the state of a group of checkboxes?

I am trying to keep track of which boxes are checked in my local state(you can check multiple boxes). I want to be able to check and uncheck the boxes and keep track of the ids of the boxes that are checked. I will do something with the values later. This is what I have so far:
import React, { Component } from 'react'
import './App.css'
import CheckBox from './CheckBox'
class App extends Component {
constructor(props) {
super(props)
this.state = {
fruits: [
{id: 1, value: "banana", isChecked: false},
{id: 2, value: "apple", isChecked: false},
{id: 3, value: "mango", isChecked: false},
{id: 4, value: "grape", isChecked: false}
],
fruitIds: []
}
}
handleCheckChildElement = (e) => {
const index = this.state.fruits.findIndex((fruit) => fruit.value === e.target.value),
fruits = [...this.state.fruits],
checkedOrNot = e.target.checked === true ? true : false;
fruits[index] = {id: fruits[index].id, value: fruits[index].value, isChecked: checkedOrNot};
this.setState({fruits});
this.updateCheckedIds(e);
}
updateCheckedIds = (e) => {
const fruitIds = [...this.state.fruitIds],
updatedFruitIds= fruitIds.concat(e.target.id);
this.setState({updatedFruitIds});
}
render() {
const { fruits } = this.state;
if (!fruits) return;
const fruitOptions = fruits.map((fruit, index) => {
return (
<CheckBox key={index}
handleCheckChildElement={this.handleCheckChildElement}
isChecked={fruit.isChecked}
id={fruit.id}
value={fruit.value}
/>
);
})
return (
<div className="App">
<h1>Choose one or more fruits</h1>
<ul>
{ fruitOptions }
</ul>
</div>
);
}
}
export default App
So basically I am able to check and uncheck the boxes, but I cannot seem to update and store the fruitIds. Here is my checkbox component also:
import React from 'react'
export const CheckBox = props => {
return (
<li>
<input key={props.id}
onChange={props.handleCheckChildElement}
type="checkbox"
id={props.id}
checked={props.isChecked}
value={props.value}
/>
{props.value}
</li>
)
}
export default CheckBox
Also if you have a cleaner ways to do this than the way I am doing it, I would love to see it.
This is what if I were to approach it I will do. I will create a one dimensional array that holds the id's of the fruits when A fruit if clicked(checked) I will add it id to the array and when its clicked the second time I check if the array already has the id I remove it. then the presence of id in the array will mean the fruit is checked otherwise its not checked So I will do something like below
this.state={
fruitsIds: []
}
handleCheckChildElement=(id) => {
//the logic here is to remove the id if its already exist else add it. and set it back to state
const fruitsIds = this.state.fruitsIds;
this.setState({fruitsIds: fruitsIds.contains(id) ? fruitsIds.filter(i => i != id) : [...fruitsIds, id] })
}
then I render the checkboxes like
<CheckBox key={index}
handleCheckChildElement={this.handleCheckChildElement}
isChecked = { this.state.fruitsIds.contains(fruit.id)}
id={fruit.id}
/>
This is because you can always use the id to get all the other properties of the fruit so there is absolutely no need storing them again.
then the checkbox component should be as follows
export const CheckBox = props => {
return (
<li>
<input key={props.id}
onChange={() => props.handleCheckChildElement(props.id)}
type="checkbox"
id={props.id}
checked={props.isChecked}
value={props.value}
/>
{props.value}
</li>
)
}
The reason you are not getting your ids updated because:
You are trying to concat a non array element to an array.
concat is used for joining two or more arrays.
updatedFruitIds = fruitIds.concat(e.target.id);
You are not updating your actual fruitIds state field. I dont know why you are using "updatedFruitIds" this variable but due to above error it will always result into a single element array.
this.setState({ updatedFruitIds });
updateCheckedIds = e => {
const fruitIds = [...this.state.fruitIds],
updatedFruitIds = fruitIds.concat([e.target.id]);
this.setState({ fruitIds: updatedFruitIds });
};
OR
updateCheckedIds = e => {
const fruitIds = [...this.state.fruitIds, e.target.id],
this.setState({ fruitIds });
};

Elements losing className

I'm trying to display an error in a form field by adding a className.
This is the render function:
render() {
return (
<div className="row row--no-margin">
<button onClick={this.validate}>Test validation</button>
{
this.props.model.map( (field, index) => {
return this.renderTextField(field);
});
}
</div>
);
}
This is the renderTextField function:
renderTextField(field, index) {
let inputClassNames = 'form-control';
if (this.state.errors.indexOf(field.name) !== -1) {
inputClassNames += ' error-required';
}
return (
<div className={field.wrapperClassName} key={field.key}>
<label className="field-label">{field.label}</label>
<input
type="text"
name={field.name}
ref={field.name}
className={inputClassNames}
onChange={this.handleChange}
value={this.state[field.name]}
/>
</div>
);
}
When i click the button to test validation, the class "error-required" is added to the input, but as soon as i type anything, it loses the class.
This is the onChange function:
handleChange(event) {
this.setState({
[event.target.name] : event.target.value
});
}
The field gets its data from an object:
{
key : 'name',
name : 'name',
type : 'text',
label : 'Full Name',
wrapperClassName: 'col-md-6',
},
Am i missing something?
EDIT:
validate function:
validate() {
let errors = [];
this.props.model.map((m, index) => {
if(!this.state[m.name]){
errors.push(m.name);
}
});
this.setState({
errors: errors
})
}
I would suggest separating the form's "field state", from your "validation state", to avoid potential conflicts in the case that you have a field with name "error".
If your form has a field with name "error", changing it's value will cause your validation state to be replaced, and will produce errors/unexpected results.
Consider making the following adjustments:
// in renderTextField() use this.state.form[field.name]
<input
type="text"
name={field.name}
ref={field.name}
className={inputClassNames}
onChange={this.handleChange}
value={this.state.form[field.name]}
/>
And in handleChange(event) consider revising it to:
handleChange(event) {
const form = { ...this.state.form, [event.target.name] : event.target.value }
this.setState({
form : form
})
}
Note, you will also need to initialise your component state to include/define the form object to track the state of fields.

Select React Component data option not appeard

I am facing an issue with representing data inside select-react Component, I had successfully getting data form server side (node js ) by componentDidMount()
componentDidMount(){
fetch('api/transporationTypes')
.then( res => res.json())
.then(trasnportation => this.setState({trasnportation }, () => console.log(trasnportation)));
}
but I cannot set loaded data inside React-Select Component I tried the below here below how this component works with static data.
render() {
const { selectedOption } = this.state;
return (
<Select
name="form-field-name"
value={selectedOption}
onChange={this.handleChange}
options={[
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two' },
]}
/>
);
}
}
but when I trying to load dynamic data with code below it represent No results Found see screenshot: http://prntscr.com/jqvp76.
alos printing the data via console.log('optionItems',optionItems) in dev tools print correctly http://prntscr.com/jqvq7v
How can I make option of select component works successfully
render() {
const { selectedOption } = this.state.selectedOption;
let optionItems = this.state.trasnportation.map((trans) =>
[ {value: `${trans.TransportationType}` , label : `${trans.TransportationType}`}]
);
console.log('optionItems',optionItems)
return (
<div className="row">
<h1>Choose Tranportation Type</h1>
<Select className="col-md-8"
name="form-field-name"
value={selectedOption}
onChange={this.handleChange1}
option={optionItems}
placeholder = "Select one of below"/>
</div>
);
}
}
Thanks -- Fadi
The items have wrong type:
let optionItems = this.state.trasnportation.map((trans) =>
[ {value: `${trans.TransportationType}` , label : `${trans.TransportationType}`}]
);
to
let optionItems = this.state.trasnportation.map((trans) =>
({value: `${trans.TransportationType}` , label : `${trans.TransportationType}`})
); // [] -> ()
It likely has something to do with the typos, in the code supplied there is:
trasnportation
transporation
transportation

Resources