- React Components
- Christopher Pitt
- 1057字
- 2021-07-09 19:34:45
Shared component actions
So, how do we change from a PageView
class to a PageEditor
class? For that, we need to hook into browser events and fiddle with the state:
class Page extends React.Component { constructor(props) { super(props); this.state = { "isEditing": false }; } render() { if (this.state.isEditing) { return <PageEditor {...this.props} onCancel={this.onCancel.bind(this)} />; } return <PageView {...this.props} onPageEdit={this.onEdit.bind(this)} />; } onEdit() { this.setState({ "isEditing": true }); } onCancel() { this.setState({ "isEditing": false }); } }
We're providing a way for child components to call methods in parent components by passing down methods child components can use. When a PageView
class wants to put the Page
into edit mode, it can call this.props.onEdit
. The Page
will know how to handle that. We'll see this pattern often, so it's good to understand what it's doing here before moving on!
In the same way, we provide a way for a PageEditor
class to cancel edit mode. In both these cases, we use setState
to switch between editing and viewing states.
Note
We bind the handle methods, because otherwise this
will mean something different when the methods are called. Binding like this is not efficient, so we'll revisit this later with an alternative!
We can connect these handlers to click events in each component:
class PageEditor extends React.Component {
render() {
return <form>
<input type="text" name="title" />
<textarea name="body"></textarea>
<button>save</button>
<button
onClick={this.onCancel.bind(this)}
>back</button>
</form>;
}
onCancel(event) {
event.preventDefault();
this.props.onCancel();
}
}
We need to prevent default form submission before calling the onCancel
passed down through props. The code is as follows:
class PageView extends React.Component {
render() {
return <p>
{this.props.title}
<button
onClick={this.props.onEdit}
>edit</button>
<button>delete</button>
</p>;
}
}
You should now be able to run this in a browser and toggle between the edit and view aspects of each page. This is a good time to stop and take stock of what we've achieved:
- We created an entry-point component to page management called
PageAdmin
. This component handles fetching and persisting page data. It uses aBackend
class to do these. It also rendersPage
components for each page thatBackend
returns. - We created a
Page
component to encapsulate page data as well as edit and view aspects of each page. ThePage
component handles switching between these two child components, via callbacks. - We created
PageEditor
as an interface for editing page data. It contains a couple of fields, which we'll shortly discuss. - Finally, we created
PageView
as an interface for viewing page data and getting to the edit mode. We're about to make the Delete button work too.
If you've been following along, your interface may look something like this:

We have created new function references throughout this chapter. Every time we use fn.bind(this)
, we create a new function. This is inefficient if we're doing it inside render methods. We can get around this by creating a base component:
import React from "react"; class Component extends React.Component { bind(...methods) { methods.map( method => this[method] = this[method].bind(this) ) } } export default Component;
If we extend this base component (instead of the usual React.Component
), then we will have access to the bind
method. It takes one or more function names, and replaces them with bound versions.
Now, we need to add event handlers for updating and deleting pages. Let's start with PageView
and PageEditor
:
import Component from "component"; class PageView extends Component { constructor(props) { super(props); this.bind( "onDelete" ); } render() { return <p> {this.props.title} <button onClick={this.props.onEdit} >edit</button> <button onClick={this.onDelete} >delete</button> </p>; } onDelete() { this.props.onDelete( this.props.id ); } }
We added an onClick
handler to the Delete button. This will trigger a bound version of onDelete
in which we pass the correct:
import Component from "component"; class PageEditor extends Component { constructor(props) { super(props); this.bind( "onCancel", "onUpdate" ); } render() { return <form> <input type="text" name="title" value={this.props.title} onChange={this.onUpdate} /> <textarea name="body" value={this.props.body} onChange={this.onUpdate}> </textarea> <button onClick={this.onCancel}> cancel </button> </p>; } onUpdate() { this.props.onUpdate( this.props.id, event.target.name, event.target.value ); } onCancel(event) { event.preventDefault(); this.props.onCancel(); } }
Here, we added onUpdate
so that we can determine which input changed. It calls the props onUpdate
method with the correct property name and value.
We also add the name
and value
attributes for the inputs, setting the values to the corresponding properties. These updates are triggered when the inputs change, calling the onUpdate
method. This means property updates will reflect in the fields.
Where do these new handler properties come from? We need to add them to PageAdmin
:
import Component from "component"; class PageAdmin extends Component { constructor(props) { super(props); this.state = { "pages": [] }; this.bind( "onUpdate", "onDelete" ); } componentWillMount() { this.setState({ "pages": this.props.backend.getAll() }); } render() { return <ol> {this.state.pages.map(function(page) { return <li key={page.id}> <Page {...page} onUpdate={this.onUpdate} onDelete={this.onDelete} /> </li>; })} </ol>; } onUpdate(...params) { this.props.backend.update(...params); } onDelete(...params) { this.props.backend.delete(...params); } }
Finally, we create a couple of methods to handle updates and deletes. These are bound, as we've been doing to methods in the other classes. They also use the rest/spread operators as a bit of a shortcut!
We can fake the backend data and operations with an array of pages and a few array modifier methods:
class Backend { constructor() { this.deleted = []; this.updates = []; this.pages = [ { "id": 1, "title": "Home", "body": "..." }, { "id": 2, "title": "About Us", "body": "..." }, { "id": 3, "title": "Contact Us", "body": "..." }, { "id": 4, "title": "Products", "body": "..." } ]; } getAll() { return this.pages .filter(page => { return this.deleted.indexOf(page.id) == -1 }) .map(page => { var modified = page; this.updates.forEach((update) => { if (update[0] == page.id) { modified[update[1]] = update[2]; } }); return modified; }); } update(id, property, value) { this.updates.push([id, property, value]); } delete(id) { this.deleted.push(id); } }
Tip
This is by no means an efficient implementation. Please do not use this code in production. It's just an example interface against which we can test our code!
The all
method returns a filtered and mapped array of initial pages. The () => {}
syntax is a shortcut for (function(){}).bind(this)
. The brackets are even optional, if there is exactly one property for the function. The filter checks that each page id
is not in the deleted
array. We're not actually deleting pages in this pretend backend. We're simply excluding ones we know we don't want to see.
We don't update the pages directly, but we apply updates to the array before all
returns it. This isn't efficient, but it does allow us to see out interface in action.
Note
You can learn more about these array tricks at https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array. It's a great place to learn about JavaScript language features.
- Mastering AWS Lambda
- 新一代通用視頻編碼H.266/VVC:原理、標準與實現(xiàn)
- Mastering Python Scripting for System Administrators
- 薛定宇教授大講堂(卷Ⅳ):MATLAB最優(yōu)化計算
- SAP BusinessObjects Dashboards 4.1 Cookbook
- BeagleBone Black Cookbook
- Mastering Git
- Python深度學習原理、算法與案例
- UI設計全書(全彩)
- Visual Basic程序設計(第三版)
- 從Power BI到Analysis Services:企業(yè)級數(shù)據(jù)分析實戰(zhàn)
- 邊玩邊學Scratch3.0少兒趣味編程
- Python網(wǎng)絡爬蟲實例教程(視頻講解版)
- Web前端測試與集成:Jasmine/Selenium/Protractor/Jenkins的最佳實踐
- Hands-On ROS for Robotics Programming