Let's think about how we want to structure the components of our interface. Many content management systems feature lists of items—items that we store in and retrieve from a database. For example, let's imagine a system through which we can manage the pages of a website.
For such a system, we need an entry-point—something like PageAdmin, which connects our persistence layer to our interface:
import React from "react";
class PageAdmin extends React.Component {
render() {
return <ol>...page objects</ol>;
}
}
export default PageAdmin;
We can also represent the persistence layer in the form of a backend class:
class Backend {
getAll() {
// ...returns an array of pages
}
update(id, property, value) {
// ...updates a page
}
delete(id) {
// ...deletes a page
}
}
Note
Later, we'll look at ways of persisting this data. For now, it's OK to just use static data in this class.
We could connect PageAdmin to this class by proving an instance of Backend as a property:
var backend = new Backend();
ReactDOM.render(
<PageAdmin backend={backend} />,
document.querySelector(".react")
);
Now, we can start using the Backend data in our PageAdmin component:
The truth is that we don't really need to define a default state, or store the page objects to the state. I've done so to demonstrate the idiomatic way of defining initial component state and overriding state when working with ES6-style components.
There's a lot going on here, so let's break it down bit-by-bit:
We made a constructor. In the constructor, we defined the initial state of a component. We defined the state as an object with an empty pages array.
React will call a few magic methods in the life cycle of a component. We used componentWillMount to get an array of pages, so we have something to render. We also passed this array of pages to the setState method. This exists to store state data and update the markup of a component at the same time. The this.state.pages method will now contain the array of pages from the backend.
When we use curly braces inside markup, it acts like a dynamic value (just like with properties). We can use the Array.prototype.map method to return a new element for each page in the array of pages. This will return a new list of li components. React also expects components in a list to have a special key property, which it uses to identify them. React uses this to track which components it can remove, add, or change efficiently.
Note
The code references page.id. The pages returned by the backend should have the id, title, and body properties for these examples to work.
Let's concentrate on how to show each page through the content management system. The PageAdmin renders each page as a list item, so let's think about what we want to do inside each list item. I think it makes sense to have a non-interactive summary of each page. Think of a tabular view of all pages in a website:
Home
Products
Terms of service
Contact us
So there's one aspect to pages that is static: the view of the page title. Perhaps we can also include links to edit or delete each page.
We also want to be able to update each page. We're probably going to need some sort of form, with text inputs for each field we might want to update.
We can represent these two scenarios in a single component:
Note that we can define input elements in a way you might expect, if you've worked with HTML markup before. We'll revisit this component later, so don't worry about the details just yet.
The preview mode, for this component, is a little similar:
This raises an interesting question. How can we efficiently transfer properties from one component to another? ES6 provides a great tool for this in the form of a language feature called the spread operator. First, we need to provide pages to page components in PageAdmin:
We're replacing a new page with the Page component we created earlier. We use the spread operator to assign each object key as a component property. We can repeat this concept in Page:
The {...this.props} expands the page object keys. The page.id becomes this.props.id inside the PageEditor and PageView components. This method is great for transferring many properties; we don't need to write out each one.