Something weird is happening when I try to us select in reactjs.
Here is my code:
onDropDownChange=(e)=>{
let val = this.props.contactUsReducer.contactUsList.filter(function(item) {
return item.topic == e.target.value
});
let key= e.target.value;
let name= e.target.name;
console.log("key "+key+" val: "+val[0].displayName+" name:"+name);
this.setState(prevState => {
return ( {...prevState, [name]:key, displayName: val[0].displayName})
}, function () {
console.log(this.state);
});
}
and in my render I have
<select type="select" className="text-input flex-row-right-item" onChange={this.onDropDownChange} name="topic" value={this.state.displayName}>
{this.createSelectItems(this.props.contactUsReducer.contactUsList)}
</select>
Also here is my helper function:
createSelectItems=(itemsList)=> {
let items = [];
for (let i = 0; i < itemsList.length; i++) {
items.push(<option key={itemsList[i].topic} value={itemsList[i].topic}>{itemsList[i].displayName}</option>);
}
return items;
}
Now when I try to change the select box nothing will change in the UI so select box does not show the updated selection but I clearly see in the state that the state has been updated. Why the changes does not get reflected in select box.
Any idea?
For some reason you are supplying a function to your setState function. I personally find making a copy of the state and setting the properties to work better.
onDropDownChange = e => {
let val = this.state.myArr.filter(function(item) {
return item.topic === e.target.value;
});
let key = e.target.value;
let name = e.target.name;
console.log(
"key " + key + " val: " + val[0].displayName + " name:" + name
);
let prevState = { ...this.state };
prevState[name] = key;
prevState["displayName"] = val[0].displayName;
this.setState({ ...this.state, prevState });
console.log(this.state);
};
Change value set to displayName in your setState as,
this.setState(prevState => {
return ( {...prevState, [name]:key, displayName: key})
}, function () {
console.log(this.state);
});
Because the value prop should have the value of the selected option
The value on the select should be one of the values on the options tags, but as values on those options you're mapping the topic but you made value={this.state.displayName} on the select tag ?
if you replace it with value={this.state.topic} it will work since it uses the same data mapping.
here is a codesandbox exp.
Related
Hello i have problem about change state after onClick with this function i dont know why this is doesnt work because console.log displayed difference value and i dont know why i cant set the same state.
`doneUndone = (index) => {
console.log(!this.state.scores[index].done)
const test = !this.state.scores[index].done
this.setState({
scores: test,
})
}`
here will be all code of this aplication https://codepen.io/RetupK/pen/xxKmELd?editors=0010
As per your state scores is an array and in your method of done you are assigning Boolean value to it where as it must be an array itself. Because you're using .map() in your render method which only works with array not boolean.
What you need to do is change the done property of particular object in scores and pass the newly updated scores object to setState method and it will work.
doneUndone = (index) => {
this.state.scores[index].done = !this.state.scores[index].done
this.setState({
scores: this.state.scores,
})
}
If you use this.state to get previously done value you might have problems when you fire doneUndone method multiple times (e.g. clicking button few times in a row). That's why I suggest such solution:
doneUndone = index => {
this.setState(state => ({
scores: state.scores.map((score, idx) =>
idx === index ? { ...score, done: !score.done } : score
)
}));
};
The doneUndone method isn't updating the state properly. You can check the method form here.
doneUndone = (index) => {
const score = this.state.scores[index];
const updatedScore = {...score, done: !score.done};
const updatedScores = [...this.state.scores];
updatedScores[index] = updatedScore;
this.setState({
...this.state,
scores: updatedScores
})
}
doneUndone = (index) => {
let modScores = this.state.scores;
modScores[index].done=!this.state.scores[index].done
this.setState({
scores: modScores
})
}
cleaner way to do it
I need to add "Select All" option in my multi-select. It should be displayed if there is at least 1 filtered option. Click on "Select All" should then add only filtered options (not all options necessarily) to already selected options.
Input is empty, so all options are "filtered":
Clicking on Select All option would then add all options to selected options.
Input contains "custom", so only one option remains::
Clicking on Select All option would then add only that one option to selected options.
It was easy to add "Select all" option that adds all initial options, but that isn't the solution to my problem. I managed to partially solve my problem by manually filtering options and storing them filtered in component's state, but I hope there is a simpler solution.
I would use a combination of filterOption, InputChange and onChange props:
In InputChangeyou will catch the inputValue every time the user changes it and store it into the state. This value will be reused in onChange.
You need to change the initial filterOption to display your Select all all the time. Original logic is if inputValue is null returns true else returns true is inputValue is included in option label or value. Before doing this logic, we had an other condition where if option value corresponds to your Select all option then returns true right away.
In onChange; by default the returned options are those received. Then if an option has been selected (as it's multiple it could be removed) and this option is Select all, your returned value should be a copy of all the options and filter them by the inputValue.
Maybe there's a simplest way to do it but I think this one it pretty effective:
onChange = (opt, { option }) => {
let newOpts = opt;
let string = this.state.searchField;
if (option && option.value === "all") {
let filteredOptions = clone(options);
filteredOptions = filteredOptions.filter(
filteredOption =>
isIncludingString(string, filteredOption) &&
!newOpts.includes(filteredOption)
);
string = null;
newOpts = newOpts
.concat(filteredOptions)
.filter(newOpt => newOpt.value !== "all");
}
this.setState({
searchField: string,
values: newOpts
});
};
onInputChange = (string, { action }) => {
if (action === "input-change") {
this.setState({
searchField: string
});
}
};
filterOption = ({ label, value }, string) => {
if (value === "all") {
return true;
} else if (string) {
return label.includes(string) || value.toString().includes(string);
} else {
return true;
}
};
Important note, in my example I use clone from lodash
Below the isIncludingString function used in onChange.
function isIncludingString(string, option) {
let result = false;
if (
!string ||
option.label.toString().includes(string) ||
option.value.toString().includes(string)
) {
result = true;
}
return result;
}
Here a live example.
Here is the function that I used to convert all my existing multi selecters:
import React from "react";
import ReactSelect from "react-select";
const addSelectAllOptionIfIsMulti = (props) => {
if (!props.isMulti) {
return props;
}
const selectAllOption = {}; // just an object to be compared to by reference
const getOptionLabel = (obj) =>
obj === selectAllOption
? "Select all"
: props.getOptionLabel
? props.getOptionLabel(obj)
: obj.label;
const getOptionValue = (obj) =>
obj === selectAllOption
? selectAllOption
: props.getOptionValue
? props.getOptionValue(obj)
: obj.value;
const onChange = (values) => {
const selectedOptions = values.includes(selectAllOption)
? props.options
: values;
props.onChange(selectedOptions);
};
const options =
props.options.length === props.value?.length
? props.options
: [selectAllOption, ...props.options];
return { ...props, getOptionLabel, getOptionValue, onChange, options };
};
function Select({...props}){
return <ReactSelect {...addSelectAllOptionIfIsMulti(props)}/>
}
export default Select;
I have a datalist , on change I have a function , that checks value was picked from options or typed new value. Depending on that I'm assigning it to variable "address_id" or "new_address" . In the end I need to make object and store it in state. The problem is I need to put only one value that is not null.
If address_id is null, so in object should be new_address. And if new_address is null, in object address_id.
address_id=null
tempObj: {
new_address: new_address,
},
How to make validation which checks what variable is not null and put it in object.
onChooseAddress(e, idx) {
const { adresses } = this.state
let address_id = null
let new_address = null
for (let i = 0; i < adresses.length; i++) {
if (
e.target.value === adresses[i].address &&
e.target.name === "address"
) {
address_id = adresses[i].id
}
}
if (!address_id && e.target.name === "address") {
new_address = e.target.value
}
// Here I need make validation!!!
this.setState(prevState => ({
tempObj: {
...prevState.tempObj,
// address_id: address_id or new_address: new_address,
},
}))
}
You can just do a normal function that returns the state object.
this.setState(prevState => {
// do validations here
return {
tempObj: {
...prevState.tempObj,
// address_id: address_id or new_address: new_address,
};
});
Something like this?
let existingaddr = addresses.find( (address) => e.target.value ===
address.address && e.target.name === 'address' )
let tmpObj = {
...prevState.tempObj
}
if( existingaddr ){
tmpObj.address_id = existingaddr.id;
}
else{
tmpObj.new_address = e.target.value
}
this.setState(prevState => (tmpObj))
I didnt test it, but the idea is to create an object, which will have one key or the other, and then spread in into your temp object. Hopefully I understand what you were trying to accomplish.
EDIT:
I changed it up a bit and put it in a code pen. It follows your usecase as far as I can tell.
I have the array of strings and i need to filter this array due to the input value. I filter it through filter function, but when i clear input the array stays filtered. How get an initial array?
filterCoins = (e) => {
let updatedList = this.state.data;
updatedList = updatedList.filter(function(item){
return item.toLowerCase().search(
e.target.value.toLowerCase()) !== -1;
});
this.setState({data: updatedList});
};
Solution depends on how you're getting the initial data.
If it's from props, better have it into state for populating filteredList.
updatedList = this.props.data.filter(function(item){
return item.toLowerCase().search(
e.target.value.toLowerCase()) !== -1;
});
If it's self contained state component, then use class variable this.data to store initial data filter the searched item from this.data;
updatedList = this.data.filter(function(item){
return item.toLowerCase().search(
e.target.value.toLowerCase()) !== -1;
});
this.setState({data: updatedList});
So the suggestion is always filter from the original list.
The original data is currently being replaced in state whenever the input value is changed.
This means you either need to keep the old data around in state so that you can revert back to it when input is cleared or come upon another way to do the implementation.
I would set the input value in state in place of updating data when the input value changes.
setCoinFilter = (e) => {
this.setState({ filterText: e.target.value });
}
Then in the component rendering the items; the filter operation can be performed.
state = {
data: [],
filterText: '',
}
filterCoins(coins, filterText) {
return coins.filter(function(coin) {
return coin.toLowerCase(filterText.toLowerCase()).search() !== -1;
});
}
shouldNotApplyFilter() {
const { filterText } = this.state;
return filterText === null || filterText === '';
}
render() {
const data = this.shouldNotApplyFilter() ? this.state.data :
filterCoins(this.state.data, this.state.filterText)
// render data
}
when your input is cleared, e.target.value will be empty, then you need to reset this.state.data to its original value. I assumed the original value is kept in the state, so you can write the filtercoins function like this
filterCoins = (e) => {
let newData;
if (e.target.value) {
newData = this.state.data.filter(item =>
item.toLowerCase().indexOf(e.target.value.toLowerCase()) !== -1
);
} else {
newData = this.state.originalData;
}
this.setState({data: newData});
};
I'm attempting to created a form validation using react-bootstrap, and would like to avoid making a validation function for each input.
I have this input:
<Input
hasFeedback
type="text"
label="Username"
bsStyle={this.state.UsernameStyle}
onChange={this.handleChange.bind(this, 'Username')}
value={this.state.Username}
bsSize="large"
ref="inptUser"
placeholder="Enter Username"
/>
Plus, I have two function to handle the validation and updating of the state:
handleChange: function(name, e) {
var state = {};
state[name] = e.target.value;
this.setState(state);
var namestyle = name + 'Style';
this.setState(this.validationState(namestyle, e));
},
&
validationState: function(style, event) {
var length = event.target.value.length;
if (length > 4) this.setState({style: 'success'});
else if (length > 2) this.setState({style: 'warning'});
return {style};
}
At this point if I change style to Username I can get this solution to work for that specific input as well. I could make an if statement and depending on the string I get in style I can call the appropriate setState, but is their a better way to do this?
Thanks!
var yourVariable = "name";
this.state[yourVariable];
is equivelant to:
this.state.name;
if you want to use your variable in set state you can use this:
var value = "peter";
this.setState({[yourVariable]: value});
is equivelant to:
this.setState({name: value)};
handleChange should collect all of the information required to change state and call setState a single time. vaslidationState should not be changing state. It should just be returning the new style:
validationState(value) {
return (value.length > 4) ? "success" : (value.length > 2) ? "warning" : "error";
}
handleChange(name, e) {
const value = e.target.value;
const style = this.validationState(value);
const stateUpdates = {
[name]: value,
[`${name}Style`]: style
};
this.setState(stateUpdates);
}