I am very new to react and am trying to create a checkbox and also display the selected checkbox names list in a textbox. I am using selected[] to store the values for the selected box and checked[] to store whether that box is checked or not.If it is checked I update the value of selected accordingly.
The code as of now works fine but I want to avoid use of forceupdate() and use setState(). When I use I am unable to update the selected[] value using it. Can somebody tell me of how to update the particular array index value using setstate so thatit gets render itself and I do not have to use forceupdate() ?
thank you.
var history = React.createClass({
getInitialState : function(){
return {
checked : [],
selected: []
};
},
componentWillMount : function(){
},
handleChangechk: function (e){
const target = e.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
if(value===true)
{
this.state.checked[name]= true;
this.state.selected[name] = name;
this.forceUpdate();
}
else
{
this.state.checked[name]= false;
this.state.selected[name] = '';
this.forceUpdate();
}
},
render : function() {
var historyList = [];
var selectedList = [];
for (var i = 0; i < 10; i++) {
historyList.push(<span key={i}><input type="checkbox" name = {i} checked={!!this.state.checked[i]} onChange ={(e)=> this.handleChangechk(e)}/><span ></span><label >checkbox {i}</label></span>);
if(this.state.selected[i])
{
selectedList.push(this.state.selected[i]);
}
};
return( /* display selected checkbox (selectedList ); */}});
Never mutate the state variable directly by this.state.a.push() or this.state.a = '', always use setState to update the value.
From Doc:
Never mutate this.state directly, as calling setState() afterwards may
replace the mutation you made. Treat this.state as if it were
immutable.
forceUpdate() is not required if you update the state values properly, it will automatically do the re-rendering, Check this:
class App extends React.Component{
constructor(){
super();
this.state = {value: [], selected: []}
}
change(event) {
let target, value, selected;
target = event.target;
value = this.state.value.slice();
selected = this.state.selected.slice();
value[target.name] = target.checked;
if(target.checked){
selected.push(target.name);
}else{
let index = selected.indexOf(target.name);
selected.splice(index, 1);
}
this.setState({
value, selected
});
}
render(){
return(
<div>
{[1,2,3,4,5].map((el,i)=>{
return <div key={i}>
<input checked={this.state.value[i]} type='checkbox' onChange={this.change.bind(this)} name={i}/>
Item- {el}
</div>
})}
Selected Values: {JSON.stringify(this.state.selected)}
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'))
<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='app'/>
You could get a copy of the checked and selected arrays, change the relevant index and then update the state:
handleChangechk: function (e) {
const target = e.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
let checked = this.state.checked.slice(),
selected = this.state.selected.slice();
if(value === true) {
checked[name]= true;
selected[name] = name;
} else {
checked[name]= false;
selected[name] = '';
}
this.setState({ items, selected });
}
You should almost never assign values to your state directly and/or use forceUpdate:
this.state.checked[name]= true;
this.state.selected[name] = name;
this.forceUpdate();
Avoiding the use of forceUpdate is as simple as using setState to assign the values. setState will automatically trigger a re-render.
var newChecked = this.state.checked.slice();
newChecked[name] = true;
var newSelected = this.state.selected.slice();
newSelected [name] = name;
this.setState({
checked: newChecked,
selected: newSelected,
});
Related
I'm a beginner to React; I understand that setState is asynchronous, but I don't understand why in the example pen below the box below the refresh is not done immediately, and only updates after the second character is input.
Codepen: (updated to link to correct pen)
https://codepen.io/anon/pen/odZrjm?editors=0010
Portion:
// Returns only names matching user's input
filterList = (term) => {
let finalList = []
for (let x = 0; x < this.state.names.length; x++) {
if (this.state.names[x].toLowerCase().includes(term)) {
finalList.push(this.state.names[x])
}
}
finalList.sort()
return finalList
}
// Returns the name list as string
listNames = () => {
let filteredNames = [], filter = this.state.filter.toLowerCase(), names = ""
if (filter === "") return "Enter filter chars"
// Generate filtered array with only names matching filter
filteredNames = this.filterList(filter)
// Build and return comma-separated list of filtered names
names = filteredNames.join(', ')
if (names.length === 0) return 'None found'
return names
}
handleChange = (event) => {
let result, alert = 'alert-primary'
result = this.listNames()
this.setState({
filter: event.target.value,
namesList: result
})
}
If I replace the line "result = this.listNames()" in handleChange with just "result = 'test'" then it updates immediately. Can you please explain why it does not when I call the functions?
It occurs because you are calling the listNames method before this.state.filter is set, thus reaching:
if (filter === "") return "Enter filter chars"
Here's a working pen: https://codepen.io/anon/pen/gzmVaR?editors=0010
This is because listNames() is being called before setState
when you call handleChange method, it can't find 'this' scope.
so 'this' is undefined and can't call this.listNames().
solution:
use onChange={this.handleChange.bind(this)}
instead of onChange={this.handleChange}
i want to create state like this:
componentWillReceiveProps(nextProps) {
nextProps.columns.forEach((c) => {
const name = nextProps.columns[nextProps.columns.indexOf(c)];
this.setState({ `${name}`: (this.props.activeHeaders.indexOf(c) > -1) });
console.log(`${name}`);
});
}
I am mapping on my array columns, so each item on the array, i want to set state on them as key, is there a possibe way?
Is there a possible way?
Yes, but the way you are trying is not correct, instead of calling setState inside loop, first prepare an object with all the key-value, then pass that object to setState.
Like this:
componentWillReceiveProps(nextProps) {
let obj = {};
nextProps.columns.forEach((c, i) => {
const name = nextProps.columns[nextProps.columns.indexOf(c)];
obj[name] = this.props.activeHeaders.indexOf(c) > -1;
});
this.setState(obj);
}
Didn't get the meaning of this line:
const name = nextProps.columns[nextProps.columns.indexOf(c)];
componentWillReceiveProps(nextProps) {
nextProps.columns.forEach((c) => {
const name = nextProps.columns[nextProps.columns.indexOf(c)];
this.setState({ [name]: (this.props.activeHeaders.indexOf(c) > -1) });
console.log(`${name}`);
});
}
This should do the job
I have simple react input component that accepts only numbers, each keyboard input i check inputs and return validated entries only, after 3rd character I add - character to the input. everything works fine but when i edit / remove one char from first third character my caret jumps to last position its annoying, how can i keep caret at the same position.
Link to working example
My input component
class App extends Component {
constructor(props){
super(props)
this.state = {
value: ''
}
}
render() {
const {value} = this.state
return (
<div className="App">
<input type="text" value={value} onChange={this._onChange}/>
</div>
)
}
_onChange = (e) => {
this.setState({value: this._validate(e.target.value)})
}
_validate(string){
return new Input(string)
.filterNumbersOnly()
.addSeparator()
.getString()
}
}
export default App;
Here is my string validation class
export default class Input{
constructor(string){
this.string = string
}
filterNumbersOnly(){
const regex = /(\d)/g
,strArray = this.string.match(regex)
this.string = strArray ? strArray.reduce((a,b) => { return `${a}${b}` }, '') : ''
return this
}
addSeparator(){
const charArray = this.string.split('')
this.string = charArray.reduce((a,b,index) => {
if(index > 2 && index < 4) {
return `${a}-${b}`
}
return `${a}${b}`
}, '')
return this
}
getString(){
return this.string
}
}
There is way if you could preserve the selection range and element target and then apply it after the set state i.e on set state callback like this:
_onChange = (e) => {
var r =[e.target.selectionStart,e.target.selectionEnd];
var el = e.target;
this.setState({value: this._validate(e.target.value)},()=>{
//just to make sure you extend cursor in case of - addition
if (r[1] === this.state.value.indexOf("-")+1)
r[1]++;
el.setSelectionRange(r[0], r[1]);
})
}
Sample url https://codesandbox.io/s/628q1rvnl3
It resolve all my problem, init selection in callback setState
let el = ev.target
this.setState({
sum: value
}, () => {
el.setSelectionRange(1, 1)
})
I'm trying to build a connect 4 game, which has a Board component comprised of 7 Column components all contain 6 Space components. Every Column has a Drop button above it, which will be used to drop the piece into the column. In order to alternate between "red" and "black" players, I have created a state "redOrBlue" which returns a boolean.
In a nutshell, when the Drop button is clicked, I want to toggle the value of "redOrBlue" to keep track of whose turn it is. I've set the onClick function to setState({redOrBlue: !this.state.redOrBlue)}, however, calling this function will cause react to render an extra column right below the column in which the button was clicked. I understand that setState automatically re-renders the DOM, but how can I keep from it rendering duplicates of a component? Thanks for your help!
class Column extends Component{
constructor(){
super();
this.state = {
myArray: [],
buttonArray: [],
buttonNumber: null,
newArray: [],
redOrBlue: true
}
}
makeRow(){
var component = <Space className="space"/>
for(var i = 0; i < 6; i++){
this.state.myArray.push(component);
}
}
handleClick(){
var num = this.props.colNumber;
var array = this.props.boardArray[num];
var boolean = false;
var color;
if(this.state.redOrBlue === true){
color = "red"
}else{
color = "black"
}
for(var i = 5; i > -1; i--){
if(boolean === false){
if(array[i] === null){
array[i] = color;
boolean = true;
}
}
}
this.setState({redOrBlue: !this.state.redOrBlue})
console.log(array)
}
render(){
{this.makeRow()}
return(
<div className="column">
<DropButton onClick={() => this.handleClick()} id={'button-' + this.props.colNumber} buttonNumber={this.props.colNumber} className={this.props.className}/>
{this.state.myArray.map(function(component, key){
return component
})}
</div>
)
}
}
There are many things that you need to change in your code:
*First of all never store the ui items in state variable, state variable should contain only data and values.
*Never do any changes in state variable by this.state.a = '' or this.state.a.push({}), always treat the state values as immutable and use only setState to change the value.
*Always call function inside render that will create the ui part directly if you want to create something dynamically.
Call makeRow method from render and it will return the ui directly without storing it in state variable, like this:
makeRow(){
var component = [];
for(var i = 0; i < 6; i++){
component.push(<Space key={i} className="space"/>);
}
return component;
}
render(){
return(
<div className="column">
<DropButton onClick={() => this.handleClick()} id={'button-' + this.props.colNumber}
buttonNumber={this.props.colNumber} className={this.props.className}/>
{this.makerow()}
</div>
)
}
Remove {this.makeRow()} from your render function. All you're doing is adding another row to the state, in a rather non-kosher method, every time the component renders. Try something like this:
constructor(){
super();
this.state = {
myArray: [],
buttonArray: [],
buttonNumber: null,
newArray: [],
redOrBlue: true
}
this.makeRow();
}
makeRow(){
var tempArray = [];
var component = <Space className="space"/>
for(var i = 0; i < 6; i++){
tempArray.push(component);
}
this.setState({ myArray: tempArray }};
}
Have filter, it's filtering ok, but when clear input. I see filtered result:
filterList(event) {
var updatedList = this.state.items;
if (event.target.value !== '') {
updatedList = updatedList.filter(function(item){
return item.name.toLowerCase().indexOf(event.target.value.toLowerCase()) !== -1;
});
}
this.setState({items: updatedList}); // now this.state.items has a value
}
My condition not working.
https://plnkr.co/edit/dPZM9BZVa4uzEMwdNpZ8?p=catalogue full component in script.js
There's a better approach for this. You usually don't want to use props to set your state directly. What you want is to only use the state for filtered item sets, since your props will always contain the full set of items. So first, remove your componentWillReceiveProps function. Then change the following things:
// in your constructor
this.state = {
filteredItems: false
}
filterList(e) {
var value = e.target.value;
this.setState({
filteredItems: !value
? false
: this.props.items.filter(function(item) {
return item.name.toLowerCase().indexOf(value.toLowerCase()) > -1
})
})
}
// inside render
var elems = this.state.filteredItems || this.props.items