Hard-coding data in our applications isn't exactly ideal. Today, we'll set up our components to be driven by data to them access to external data.
Through this point, we've written our first components and set them up in a child/parent relationship. However, we haven't yet tied any data to our React components. Although it's a more pleasant experience (in our opinion) writing a website in React, we haven't taken advantage of the power of React to display any dynamic data.
Let's change that today.
Going data-driven
Recall, yesterday we built the beginning of our timeline component that includes a header and an activity list:
We broke down our demo into components and ended up building three separate components with static JSX templates. It's not very convenient to have to update our component's template everytime we have a change in our website's data.
Instead, let's give the components data to use to display. Let's start with the <Header />
component. As it stands right now, the <Header />
component only shows the title of the element as Timeline
. It's a nice element and it would be nice to be able to reuse it in other parts of our page, but the title of Timeline
doesn't make sense for every use.
Let's tell React that we want to be able to set the title to something else.
Introducing props
React allows us to send data to a component in the same syntax as HTML, using attributes or properties on a component. This is akin to passing the src
attribute to an image tag. We can think about the property of the <img />
tag as a prop
we're setting on a component called img
.
We can access these properties inside a component as this.props
. Let's see props
in action.
Recall, we defined the <Header />
component as:
class Header extends React.Component {
render() {
return (
<div className="header">
<div className="menuIcon">
<div className="dashTop"></div>
<div className="dashBottom"></div>
<div className="circle"></div>
</div>
<span className="title">Timeline</span>
<input
type="text"
className="searchInput"
placeholder="Search ..." />
<div className="fa fa-search searchIcon"></div>
</div>
)
}
}
When we use the <Header />
component, we placed it in our <App />
component as like so:
<Header />
We can pass in our title
as a prop as an attribute on the <Header />
by updating the usage of the component setting the attribute called title
to some string, like so:
<Header title="Timeline" />
Inside of our component, we can access this title
prop from the this.props
property in the Header
class. Instead of setting the title statically as Timeline
in the template, we can replace it with the property passed in.
class Header extends React.Component {
render() {
return (
<div className="header">
<div className="menuIcon">
<div className="dashTop"></div>
<div className="dashBottom"></div>
<div className="circle"></div>
</div>
<span className="title">
{this.props.title}
</span>
<input
type="text"
className="searchInput"
placeholder="Search ..." />
<div className="fa fa-search searchIcon"></div>
</div>
)
}
}
We've also updated the code slightly to get closer to what our final
<Header />
code will look like, including adding asearchIcon
and a few elements to style themenuIcon
.
Now our <Header />
component will display the string we pass in as the title
when we call the component. For instance, calling our <Header />
component four times like so:
<Header title="Timeline" />
<Header title="Profile" />
<Header title="Settings" />
<Header title="Chat" />
Results in four <Header />
components to mount like so:
Pretty nifty, ey? Now we can reuse the <Header />
component with a dynamic title
property.
We can pass in more than just strings in a component. We can pass in numbers, strings, all sorts of objects, and even functions! We'll talk more about how to define these different properties so we can build a component api later.
Instead of statically setting the content and date let's take the Content
component and set the timeline content by a data variable instead of by text. Just like we can do with HTML components, we can pass multiple props
into a component.
Recall, yesterday we defined our Content
container like this:
class Content extends React.Component {
render() {
return (
<div className="content">
<div className="line"></div>
{/* Timeline item */}
<div className="item">
<div className="avatar">
<img src="http://www.croop.cl/UI/twitter/images/doug.jpg" />
Doug
</div>
<span className="time">
An hour ago
</span>
<p>Ate lunch</p>
<div className="commentCount">
2
</div>
</div>
{/* ... */}
</div>
)
}
}
As we did with title
, let's look at what props
our Content
component needs:
- A user's avatar image
- A timestamp of the activity
- Text of the activity item
- Number of comments
Let's say that we have a JavaScript object that represents an activity item. We will have a few fields, such as a string field (text) and a date object. We might have some nested objects, like a user
and comments
. For instance:
{
timestamp: new Date().getTime(),
text: "Ate lunch",
user: {
id: 1,
name: 'Nate',
avatar: "http://www.croop.cl/UI/twitter/images/doug.jpg"
},
comments: [
{ from: 'Ari', text: 'Me too!' }
]
}
Just like we passed in a string title to the <Header />
component, we can take this activity object and pass it right into the Content
component. Let's convert our component to display the details from this activity inside it's template.
In order to pass a dynamic variable's value into a template, we have to use the template syntax to render it in our template. For instance:
class Content extends React.Component {
render() {
const {activity} = this.props; // ES6 destructuring
return (
<div className="content">
<div className="line"></div>
{/* Timeline item */}
<div className="item">
<div className="avatar">
<img
alt={activity.text}
src={activity.user.avatar} />
{activity.user.name}
</div>
<span className="time">
{activity.timestamp}
</span>
<p>{activity.text}</p>
<div className="commentCount">
{activity.comments.length}
</div>
</div>
</div>
)
}
}
We've use a little bit of ES6 in our class definition on the first line of the
render()
function called destructuring. The two following lines are functionally equivalent:// these lines do the same thing const activity = this.props.activity; const {activity} = this.props;
Destructuring allows us to save on typing and define variables in a shorter, more compact way.
We can then use this new content by passing in an object as a prop instead of a hard-coded string. For instance:
<Content activity={moment1} />
Fantastic, now we have our activity item driven by an object. However, you might have noticed that we would have to implement this multiple times with different comments. Instead, we could pass an array of objects into a component.
Let's say we have an object that contains multiple activity items:
const activities = [
{
timestamp: new Date().getTime(),
text: "Ate lunch",
user: {
id: 1, name: 'Nate',
avatar: "http://www.croop.cl/UI/twitter/images/doug.jpg"
},
comments: [{ from: 'Ari', text: 'Me too!' }]
},
{
timestamp: new Date().getTime(),
text: "Woke up early for a beautiful run",
user: {
id: 2, name: 'Ari',
avatar: "http://www.croop.cl/UI/twitter/images/doug.jpg"
},
comments: [{ from: 'Nate', text: 'I am so jealous' }]
},
]
We can rearticulate our usage of <Content />
by passing in multiple activities instead of just one:
<Content activities={activities} />
However, if we refresh the view nothing will show up! We need to first update our Content
component to accept multiple activities. As we learned about previously, JSX is really just JavaScript executed by the browser. We can execute JavaScript functions inside the JSX content as it will just get run by the browser like the rest of our JavaScript.
Let's move our activity item JSX inside of the function of the map
function that we'll run over for every item.
class Content extends React.Component {
render() {
const {activities} = this.props; // ES6 destructuring
return (
<div className="content">
<div className="line"></div>
{/* Timeline item */}
{activities.map((activity) => {
return (
<div className="item">
<div className="avatar">
<img
alt={activity.text}
src={activity.user.avatar} />
{activity.user.name}
</div>
<span className="time">
{activity.timestamp}
</span>
<p>{activity.text}</p>
<div className="commentCount">
{activity.comments.length}
</div>
</div>
);
})}
</div>
)
}
}
Now we can pass any number of activities to our array and the Content
component will handle it, however if we leave the component right now, then we'll have a relatively complex component handling both containing and displaying a list of activities. Leaving it like this really isn't the React way.
ActivityItem
Here is where it makes sense to write one more component to contain displaying a single activity item and then rather than building a complex Content
component, we can move the responsibility. This will also make it easier to test, add functionality, etc.
Let's update our Content
component to display a list of ActivityItem
components (we'll create this next).
class Content extends React.Component {
render() {
const {activities} = this.props; // ES6 destructuring
return (
<div className="content">
<div className="line"></div>
{/* Timeline item */}
{activities.map((activity) => (
<ActivityItem
activity={activity} />
))}
</div>
)
}
}
Not only is this much simpler and easier to understand, but it makes testing both components easier.
With our freshly-minted Content
component, let's create the ActivityItem
component. Since we already have the view created for the ActivityItem
, all we need to do is copy it from what was our Content
component's template as it's own module.
class ActivityItem extends React.Component {
render() {
const {activity} = this.props; // ES6 destructuring
return (
<div className="item">
<div className="avatar">
<img
alt={activity.text}
src={activity.user.avatar} />
{activity.user.name}
</div>
<span className="time">
{activity.timestamp}
</span>
<p>{activity.text}</p>
<div className="commentCount">
{activity.comments.length}
</div>
</div>
)
}
}
This week we updated our components to be driven by data by using the React props
concept. In the next section, we'll dive into stateful components.