将click事件传递给嵌套组件 - React |材料的UI

时间:2017-12-02 13:42:03

标签: javascript reactjs material-ui

我遇到了问题。当您点击listItem(整个分隔的li元素)时,我想在onChange内的复选框组件上调用listItem函数。

我可以轻松地将该功能从复选框移动到父级,但我会松开checked道具。

选中复选框本身不起作用,只需打开控制台即可查看结果。我希望handleToggle函数在单击整个元素时正确触发(不仅是复选框)

  <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

修改

我根本不想在这个组件中使用state。总结 - 如何将事件从ListItem(父)传递给它的孩子(Checkbox)?

最终修改:我已经找到了解决问题的方法。不需要任何州。只需两行简单的代码。

由于复选框状态完全由redux控制,我只是将onClick func移动到ListItem元素,其中包含代码中的一行:

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

无论如何,感谢大家的帮助。赞成每一个答案。

5 个答案:

答案 0 :(得分:3)

罗比,打败了我,但我处理的状态变化略有不同。

我们的想法是管理父状态中所有复选框的状态(未选中)。然后在父级中有一个handleToggle函数,它将使用所有复选框的选中值更新父级状态。

然后将此状态作为道具传递给每个复选框。

此外,使用map中的索引作为ListItem中的键不是一个好主意。如果您在列表中添加和删除项目,React将会对哪个项目感到困惑。

以下是代码:

从“反应”中导入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"));

CodeSandbox Example

答案 1 :(得分:3)

解决此问题的一种方法可能是使用React refs来保留子<ListItem/>的引用,并可能使用uncontrolled组件来分离您的输出在这种情况下,更新会将<Checkbox />替换为<input type="checkbox"/>

然后使用onChange上的<input />直接从DOM元素本身更新复选框,或者使用引用{{1}的onClick上的<ListIem/>使用<input />更新复选框DOM元素。

...
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>
    );
  }
}

...

以下是您最初的Code Sandbox的一个分支:https://codesandbox.io/s/mox93j6nry

希望这有帮助!

答案 2 :(得分:3)

由于您计划使用Redux,我建议您根据React Redux最简单的实现。

<强>减速

const inititalState = {
  list: [ // keep all your data in redux store
    { id: 1, name: 'first', checked: false },
    { id: 2, name: 'second', checked: false },
    { id: 3, name: 'third', checked: false }
  ]
};

export function list(state = inititalState, action) {
  switch (action.type) {
    case 'CHANGE': // changing the list of items (checked property)
      return {
        ...state,
        list: state.list.map(i => 
          i.id === action.item.id ? { ...action.item, checked: action.value } : i
        ) 
      };
    default:
      return state
  }
}

<强>动作

// just the only action to change 'checked' property of a given item in the list
export function change(item, value) {
  return { type: 'CHANGE', item, value };
}

最后,组件与redux store连接:

class App extends React.Component {
  handleToggle = (item) => {
    // dispatching the "change" redux-action
    this.props.dispatch(change(item, !item.checked)); // here's the toggling logic (!)
  }

  render() {
    return (
      <List>
        {this.props.list.map((item, i) => ( // map the list from the redux store
          <ListItem key={item.id} dense button // (i)ndex for the key value would be ok too
            onClick={() => this.handleToggle(item)}> // to call the redux-action by click
            <ListItemText primary={item.name} />
            <Checkbox
              disableRipple
              checked={item.checked} // this is the main binding
            />                       // with no need of onChange handling
          </ListItem>
        ))}
      </List>
    )
  }
};

// pass the list from the redux-store to the component
function mapStateToProps(state, ownProps) {
  return { list: state.list };
}

const _App = connect(mapStateToProps)(App); // connect the component to the redux store

这是非常基本的实现,我建议您遵循最佳做法,将App组件移出index.js,状态逻辑(!item.checked)可以放在动作创建器中或者减速机等...此外,我根据您的代码框创建了一个工作演示:https://codesandbox.io/s/xpk099y40w

答案 3 :(得分:2)

您并非真的需要将checked道具传递给您的父母。您可以改为跟踪组件状态中已检查的行:

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

  isChecked(name) {
    return this.state.checked.indexOf(name) > -1
  }

  handleToggle = (evt, name) => {
    if (this.isChecked(name)) {
      this.setState({
        checked: this.state.checked.filter(i => i !== name)
      })
    } else {
      this.setState({
        checked: [...this.state.checked, name]
      })
    }
  }

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

工作sample

答案 4 :(得分:0)

看起来您只想在单击相关文本时切换复选框。

您需要做的就是将所有内容包装在<label />标记中,或者只使用相应的<label htmlFor="inputId" />属性将htmlFor标记中的文字换行:

不需要javascript魔法。