Push checkbox value to array on state onChange

10,286

Solution 1

    handleDaySelect(event){
            let day_list = this.state.days;
            let check = event.target.checked;
            let checked_day = event.target.value;
            if(check){
                this.setState({
                    days: [...this.state.days, checked_day]
                })
            }else{ 
                var index = day_list.indexOf(checked_day);
                if (index > -1) {
                    day_list.splice(index, 1);
                    this.setState({
                        days: day_list
                    })
                } 
            }
        }

Hope this helps.

Solution 2

You need to check if the value is previously there, if not you have to add it else remove it. Also you should make sure that you are writing

this.state = {
    days:[]
}

in constructor like

constructor() {
   super();
   this.state = {
      days:[]
    }
}

or if as a class Property you would write

state = {
    days:[]
}

Also don't forget to bind your handleDaySelect function .

Snippet:

handleDaySelect  = (event)  => {
   var dayArr = [...this.state.days];
   const value = event.target.value
   const index = dayArr.findIndex(day => day === value);
   if(index > -1) {
       dayArr = [...dayArr.slice(0, index), ...dayArr.slice(index + 1)]
   } else {
       dayArr.push(value);
   }
   this.setState({days: dayArr});
}

Solution 3

instead of defining a dayArr in the handleDaySelect I've declared it outside so that newly selected days can be pushed into it.Then it can be set as the new state of the app on every click (More previously on every day selection). Although you're rewriting the state every single click obviously but you'll not be losing your previous selected days.

this.state = {
    days:[]
}
let dayArr;

handleDaySelect (event) {

    /*now we should check if the selected days
    already exist so that we don't double entry it*/

    if (this.state.days.indexOf(event.target.value) < 1) {
        dayArr.push(event.target.value)
    }

    this.setState({
       days: dayArr
    })
}

I hope it helps you, it would have been better if you shared your code on js fiddle or somewhere similar.

Thank you :)

Solution 4

The Problem

setState is asynchronous.

So, when you're setting state based on a previous value, you should use functional setState.

(for information on why, here's a good read)

Based on your description, it's also possible that you're either setting up state in the wrong place or you're not binding your function handleDaySelect.

Naive Solution

class DaySelector extends Component {
  // state as a static property. You can also use `getInitialState` or set `this.state` in your constructor.
  state = {
    days: []
  }

  // use an arrow function or `bind(this)` in the constructor
  handleSelect = e => {
    // grab the checkbox value since functional setState is async and won't have access to `e` without calling persist
    const { value } = e.target
    // functional setState because we're building off of the previous state
    this.setState(prevState => ({
      // copy over any other values from state
      ...prevState,
      days: [
        ...prevState.days,
        value
      ]
    })
  }
}

However, in order to make this work, you will need to handle the deselect differently than select. So, your onChange should check whether it needs to append the new value or remove an existing value. This can be a bit tedious.

A Better Alternative

You can make your life a bit easier by just storing a map of day->boolean. (here's another question that addresses the same problem)

const DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]

class DaySelector extends Component {
  state = {
    days: DAYS.reduce(
      (state, day) => ({ ...state, [day]: false }),
      {}
    )
  }

  handleSelect = e => {
    const { name, checked } = e.target
    this.setState(prevState => ({
      ...prevState,
      days: {
        ...prevState.days,
        [name]: checked
      }
    })
  }

  render() {
    return (
      // Using Fragments from React 16.2, but divs are fine too
      <>
        {
          Object.keys(this.state.days).map(day => (
            <Fragment key={day}>
              <input 
                type="checkbox"
                name={day} 
                checked={this.state.days[day]}
                onChange={this.handleSelect}
              />
              {day}
            </Fragment>
          ))
        }
      </>
    )
  }
}

Then, to get the list of selected days, you can just do:

Object.keys(this.state.days).filter(day => this.state.days[day])

(for more info on my use of fragments, see this question and the documentation)

Share:
10,286
Admin
Author by

Admin

Updated on June 13, 2022

Comments

  • Admin
    Admin almost 2 years

    I want to allow users to select days of the week from checkboxes. When a checkbox is checked, the value of that checkbox should update this.state.days. Instead of adding to the array, it's simply overwriting this.state.days. Tried using simple spread operator, but I then get an error indicating that react cant convert null into object - this even happens when I set days to [1,2] so that it isn't undefined at the start. See change function below

    this.state = {
        days:[]
    }
    
    handleDaySelect (event) {
        if (this.state.days) {
            var dayArr = [...this.state.days]
        } else {
            var dayArr = [];
        }
        dayArr.push(event.target.value)        
        this.setState({
            days: dayArr
        })
    }
    

    Render function:

    render() {
    
        const daysOptions = ["Monday", "Tuesday", "Wednesday", "Thursday", 
        "Friday", "Saturday", "Sunday"].map((cur, ind) => {
            return (
                <div key={ind} className="checks" >
                    <label>
                        <input type="checkbox" name={cur} value={cur} 
                        onChange={this.handleDaySelect} />{cur}
                    </label>
                </div>
            )
        })
        return (
            <div id="newDeal" className="formContainer" >
    
                <div className="checkBoxContainer" >
                    {daysOptions}
                </div>
            </div>
        )
    }
    
  • Admin
    Admin about 6 years
    problem with this, is react is not recognizing this.state.days as a defined array. So [...this.state.days] results in an error: "Cannot convert null to object"
  • Chaim Friedman
    Chaim Friedman about 6 years
    that sounds like you forgot to bind your handleDaySelect method
  • Admin
    Admin about 6 years
    @ChaimFriedman thought that might have been it, but it is indeed bound.
  • Shubham Khatri
    Shubham Khatri about 6 years
    @ConnorGRoane, First of all check if state is defined properly. Second, check the method is properly binded. Check the update.
  • Alok Ranjan
    Alok Ranjan almost 4 years
    Nice approach to consider.