Reputation: 55
When handling events in a (dump) child component in React, what should be supplied to the callback passed from its (smart) parent component to make it as intended? Should it be the event or only the portion of the result that we are interested in? How does it scale when we have deeply nested components? Are there some other considerations?
Intuitively, I see benefits behind passing the whole event because (i) we can get more data from the event when handling it in the parent and (ii) it separates concerns (the dump components only render and have no logic). On the other hand, it requires the child to have a constructor to bind
the wrapper method.
I've seen both approaches used. For example, in Thinking in React the author wraps callbacks in the child component to pass values (see the code on CodePen), whereas in most of the SO posts the event is passed and its value is extracted in the parent component via event.target.value
.
Code examples
Pass event:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
this.setState({checked: event.target.checked});
}
render() {
return (
<Child checked={this.state.checked} handleClick={this.handleClick}/>
);
}
}
class Child extends React.Component {
render() {
return (
<p>
<input
type="checkbox"
checked={this.props.checked}
onChange={this.props.handleClick}
/>
{" "}
Click me
</p>
);
}
}
Pass value only (notice handleClick2
):
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(checked) {
this.setState({checked: checked});
}
render() {
return (
<Child checked={this.state.checked} handleClick={this.handleClick}/>
);
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.handleClick2 = this.handleClick2.bind(this);
}
handleClick2(event) {
this.props.handleClick(event.target.checked);
}
render() {
return (
<p>
<input
type="checkbox"
checked={this.props.checked}
onChange={this.handleClick2}
/>
{" "}
Click me
</p>
);
}
}
Upvotes: 3
Views: 160
Reputation: 24627
event.target
is part of the Web Platform standard. For example:
Lets look at an example of how events work in a tree:
<!doctype html>
<html>
<head>
<title>Boring example</title>
</head>
<body>
<p>Hello <span id=x>world</span>!</p>
<script>
function debug(target, currentTarget, eventPhase)
{
console.log("target: " + JSON.stringify(target) );
console.log("currentTarget: " + JSON.stringify(currentTarget) );
console.log("eventPhase: " + JSON.stringify(eventPhase) );
}
function test(e) {
debug(e.target, e.currentTarget, e.eventPhase)
}
document.addEventListener("hey", test, true)
document.body.addEventListener("hey", test)
var ev = new Event("hey", {bubbles:true})
document.getElementById("x").dispatchEvent(ev)
</script>
</body>
</html>
The debug function will be invoked twice. Each time the events's target attribute value will be the span element. The first time currentTarget attribute's value will be the document, the second time the body element. eventPhase attribute's value switches from CAPTURING_PHASE to BUBBLING_PHASE. If an event listener was registered for the span element, eventPhase attribute's value would have been AT_TARGET.
So it would be easy to port to something newer or renewable.
References
Upvotes: 0
Reputation: 2087
You should pass the thing that you need without the event. There is no need for the whole object unless you want to extract relevant data from the event: for example the target
or when you use the same callback for multiple elements/actions.
You won't have any performance problems and there is definitely no react-ish way to do this. Just use your judgement.
Upvotes: 1