6

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>
    )
}
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
Connor G Roane
  • 153
  • 2
  • 2
  • 10

4 Answers4

6
    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.

Araz Babayev
  • 1,752
  • 12
  • 16
4

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});
}
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
1

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 :)

ishahadathb
  • 481
  • 2
  • 13
1

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)

Luke Willis
  • 8,429
  • 4
  • 46
  • 79