Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass click event to nested component - React | Material-UI

Im having an issue. When you click listItem (whole separated li element) I want to call the onChange function on the checkbox component inside listItem.

I could easily move that function from checkbox to parent but I will loose the checked prop.

Checking the checkbox itself doesnt work, but just open the console to see the results. I want handleToggle function to fire properly when whole element is clicked (not only checkbox)

  <List>
    {['first', 'second', 'third'].map((name, i) => (
      <ListItem key={i} dense button>
        <ListItemText primary={name} />
        <Checkbox
          disableRipple
          checked={false}
          onChange={(evt, checked) => this.handleToggle(evt, checked, name)}
        />
      </ListItem>
    ))}
  </List>

Code SandBox

Edit

I don't want to use state in this component at all. Summarizing - how to pass event from ListItem (parent) to it's children (Checkbox)?

Final edit: I've found out the way how to deal with it. No state needed. Just two simple lines of code.

Since checkbox state is fully controlled by redux, I just moved the onClick func to the ListItem element with one line on code in it's body:

...dispatch(toggle(!this.props.elements[nameOfElement], name));

Anyways thanks to everyone for help. Upvoted every answer.

like image 733
T Doe Avatar asked Dec 02 '17 13:12

T Doe


2 Answers

Roby, beat me too it, but I handled the state change slightly differently.

The idea is to manage the state (checked on not) of all the check boxes in the parents state. Then you have a handleToggle function in the parent that will update the parents state with the checked value of all the checkboxes.

This state is then passed to each checkbox as a prop.

Also, it is not a good idea to use the index from map as a key in the ListItem. If you add and remove items from the list, React will get confused as to which item is which.

Here is the code:

import React from "react";

import { render } from "react-dom";
import Hello from "./Hello";

import List, {
  ListItem,
  ListItemSecondaryAction,
  ListItemText
} from "material-ui/List";
import Checkbox from "material-ui/Checkbox";

const styles = {
  fontFamily: "sans-serif",
  textAlign: "center"
};

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      list: [],
      listChecked: []
    };
  }

  componentWillMount() {
    this.setState({
      list: ["first", "second", "third"],
      listChecked: [{ first: false }, { second: false }, { third: false }]
    });
  }

  handleToggle = evt => {
    console.log(evt.target.checked);
    const name = evt.target.name;
    this.setState({ name: !this.state.listChecked[name]})
    // this.props.dispatch(x(checked, name));
  };

  render() {
    const { list } = this.state;
    return (
      <List>
        {list.map((name, i) => (
          <ListItem key={name} dense button>
            <ListItemText primary={name} />
            <Checkbox
              disableRipple
              checked={this.state.listChecked[name]}
              onChange={this.handleToggle}
            />
          </ListItem>
        ))}
      </List>
    );
  }
}

render(<App />, document.getElementById("root"));

and the CodeSandbox Example

like image 131
bluesixty Avatar answered Oct 29 '22 09:10

bluesixty


One way to tackle this issue might be to use React refs to keep a reference of the child <ListItem/>, and probably use an uncontrolled component in order to detach your output from state updates, that is in this case, replace <Checkbox /> with <input type="checkbox"/>.

Checkboxes would then be updated either directly from the DOM element itself using onChange on <input />, or through React using onClick on the <ListIem/> that references the <input /> DOM element.

...
class App extends React.Component {
  constructor(props) {
    super(props);
    this.checkboxes = [
      {
        name: "first",
        checked: false
      },
      {
        name: "second",
        checked: false
      },
      {
        name: "third",
        checked: false
      }
    ];
  }

  handleListItemClicked = (evt, name) => {
    console.log("ListItem clicked :", name);
    this.checkboxes[name].checked = !this.checkboxes[name].checked;
  };

  handleInputChanged = (evt, name) => {
    console.log("input changed, evt.target :", evt.target);
    evt.target.checked = !evt.target.checked;
  };

  render() {
    return (
      <List>
        {this.checkboxes.map(({ name }, i) => (
          <div>
            <ListItem
              key={i}
              onClick={evt => this.handleListItemClicked(evt, name)}
              dense
              button
            >
              <ListItemText primary={name} />
              <input
                type="checkbox"
                name={name}
                ref={checkbox => {
                  this.checkboxes[name] = checkbox;
                }}
                onChange={evt => this.handleInputChanged(evt, name)}
              />
            </ListItem>
          </div>
        ))}
      </List>
    );
  }
}

...

Here is a fork of your initial Code Sandbox : https://codesandbox.io/s/mox93j6nry

Hope this helps!

like image 3
Philippe Sultan Avatar answered Oct 29 '22 08:10

Philippe Sultan