I'm starting to implement the smart/dumb component pattern where the "dumb" component knows nothing about it's environment and receives all of it's data through props. What do you do when the dumb component has to submit or change data? How can it communicate with the outside world and still be "dumb"?
Here's my basic example I'm using to figure this pattern out. If I were to add and onClick
event to the MyTask
component that updated a counter in the DB, what would handle that event?
// components/MyList.jsx
import React from 'react';
export default class MyList extends React.Component {
render() {
return(
<div>
<h3>{this.props.listName}</h3>
<ul>
{this.props.tasks.map((task) => (
<MyTask key={task.id} task={task} />
))}
</ul>
</div>
);
}
}
MyList.propTypes = {
listName: React.PropTypes.string.isRequired,
tasks: React.PropTypes.array.isRequired,
}
export class MyTask extends React.Component {
render() {
return (
<li>{this.props.task.text}</li>
);
}
}
MyTask.propTypes = {
task: React.PropTypes.object.isRequired,
}
and the app:
// app.jsx
import React from 'react';
import MyList from './components/MyList.jsx'
export class TaskApp extends React.Component {
getList() {
return('Today Stuff');
}
getTasks() {
return([
{id: 1, text: 'foo'},
{id: 2, text: 'diggity'},
{id: 3, text: 'boo'},
{id: 4, text: 'bop'}
]);
}
render() {
return (
<MyList listName={this.getList()} tasks={this.getTasks()} />
);
}
}
Generally speaking you can handle this by passing function references from your 'smart' components down to your 'dumb' components. The dumb component then isn't responsible for implementing any of the logic associated with the function, just telling the smart component 'I've been clicked'.
In this case inside of your TaskApp class in app.jsx you could have your click handler:
//app.jsx
...
handleClick() {
// Update the DB counter by 1
}
...
render () {}
Then pass handleClick through your components as a prop:
<MyList listName={this.getList()} tasks={this.getTasks()} handleClick={this.handleClick} />
<MyTask key={task.id} task={task} handleClick={this.props.handleClick} />
And execute it in the MyTask component when a list element is clicked:
<li onClick={this.props.handleClick}>{this.props.task.text}</li>
Keep in mind that if the handleClick() function is making use of 'this' at all, you'll need to .bind(this) on your function reference when you pass it down (or bind it in the constructor / use ES6 fat arrow functions).
EDIT: For examples of the other ways to bind 'this', you could in the constructor of your class assign the bound function to your this.handleClick reference, so:
export default class TaskApp extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
...
...
}
Which allows you to use this.handleClick the way you would normally expect.
Or you could use ES6 fat arrow functions, which preserve the context of 'this' when they are called:
<MyList
listName={this.getList()}
tasks={this.getTasks()}
handleClick={() => this.handleClick} />