Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React, Why label not firing onChange for checkbox?

I feel like I've done this a million times, but possibly not with a mapping function. I have a collection of activities that I'm creating checkboxes with the code is as follows (__map is coming from Lodash by the way):

<div className="activity-blocks">
{
    __map(this.state.activities, (activity, i) => {
        return (
            <div key={ i } className="single-activity">
                <input id={ `register-activity-${ i }` } 
                    type="checkbox" className="register-multiselect" 
                    name="main_activity" checked={ !!activity.checked } 
                    onChange={ (e) => this.toggleActivity(e, activity.id) }
                />
                <label htmlFor={ `register-activity-${ i }` }>{ activity.name }</label>
            </div>
         )
    })
 }

My onChange handler looks like this:

toggleActivity(e, activityId) {
    let activities = { ...this.state.activities };

    __forEach(this.state.activities, (activity) => {
        if (activityId === activity.id) {
            activity = __assign(activity, { checked: e.target.checked });
            return false;
        }
    });

    this.setState({ activities: activities });
}

The handler works just fine, it's for reference mostly. My issue is that the label is not firing the handler. I have tested only the checkbox input with no label and it fires off the handler. Does anyone know why the label is not doing so?

More info: I've had this problem before but it usually turns out to be an id mismatch or something simple like that. I don't believe so this time.


Edit I have deleted pretty much all the code from my file and incorporated Slawa's answer below and it still doesn't behave as expected. Here is the code including all imports, the class structure and all unnecessary code stripped out, including styles:

import React from 'react';
import __forEach from 'lodash/forEach';
import __assign from 'lodash/assign';
import __map from 'lodash/map';

export default class RegisterActivities extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            activities: [
                {
                    id: 1,
                    name: 'label-1'
                },
                {
                    id: 2,
                    name: 'label-2'
                },
                {
                    id: 3,
                    name: 'label-3'
                }
            ],
        }
    }

    toggleActivity(e, activityId) {
        const { activities } = this.state;

        const updated = activities.map((activity) => {
            if (activity.id === activityId){
                return {
                    ...activity,
                    checked: e.target.checked
                }
            }

            return activity;
        });
    }

    render() {
        return (
            <div>
                <p>what do you do?</p>
                <div>
                {
                    __map(this.state.activities, (activity, i) => {
                        return (
                            <div key={ i }>
                                <input id={ `register-activity-${ i }` } 
                                    type="checkbox" 
                                    name="main_activity" checked={ !!activity.checked } 
                                    onChange={ (e) => this.toggleActivity(e, activity.id) }
                                />
                                <label htmlFor={ `register-activity-${ i }` }>{ activity.name }</label>
                            </div>
                        )
                    })
                }
                </div>
            </div>
        );
    }
}

Edit2 I'm playing around and decided to replce onChange with onClick and now I'm able to click the actual checkbox to hit toggleActivity. Does anyone have an idea on why onChange isn't firing?

like image 216
Martavis P. Avatar asked Oct 28 '17 19:10

Martavis P.


2 Answers

The answer to my question was found on this SO post. It turns out that my label's onClick event (behind the scenes of html/React, not coded myself) was bubbling up to the parent instead of handling the input. I needed to tell the label to stop propagating by doing onClick={ e => e.stopPropagation() }.

like image 197
Martavis P. Avatar answered Oct 19 '22 19:10

Martavis P.


I think, you have an error in your toggleActivity function, activities is array and try to convert it to object, you can find my full solution of this problem here: https://codesandbox.io/s/wqyolw50vl

  toggleActivity(e, activityId) {
    const { activities } = this.state;

    const updated = activities.map( activity => {
      if (activity.id === activityId){
        return {
          ...activity,
          checked: !activity.checked
        }
      }

      return activity;
    })

    this.setState({ activities: updated });
  }
like image 30
Slawa Eremin Avatar answered Oct 19 '22 19:10

Slawa Eremin