How to implement a news feed in React.js - Part 1
This is the beginning of a post series about React.js. The goal is to build a news feed with real-time updates. In addition to the front-end, we will also implement a back-end in Rails. This first post is an introduction to React.js. So even if you are not a Rails programmer, this post might be useful to you!
For those getting started in React, the number of technologies and tools available may seem complicated. So let’s make it very simple here to make the learning process easier.
Let’s build the application step by step and introduce some theoretical concepts of React at the most opportune moment. If you want the application source code, it’s available in this GitHub repository.
Creating the React.js Project
The easiest way to create a React project is by using the create-react-app . It brings everything we need to our React project:
- webpack - a module bundler that works very well with npm. It also includes a web server and a file watcher.
-
Babel - a javascript compiler that offers support for the latest version of javascript even when browsers do not support it yet.
-
Autoprefixer - a plugin that adds CSS vendor prefixes automatically.
- ESLint - a tool that analyzes code and points out any problems.
Let’s create the project:
$ npm install -g create-react-app
$ create-react-app news-feed
Once we are done we can enter the folder and start our server:
$ cd news-feed/
$ npm start
Done! Our server is available at http://localhost:3000 (or on another port if port 3000 is already in use).
React Components
The create-react-app
created a root component named App
. We are going to use the opporunity to introduce some concepts of React. Take a quick look at the code below:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App;
Some things we can observe right away:
- Every React component inherits from
Component
; - The
render
method is implemented; - There is a HTML-like syntax within the
render
method. This is JSX, we’ll see next.
JSX
JSX
is a syntax that React uses to describe its components. In the code of the component App
we note that JSX tag div
is actually a React component that represents the HTML tag div
. Because of this similarity, we sometimes get confused and think that “there is HTML inside javascript”.
JSX
is an extension of javascript, but in XML-style. As we will see next, for each HTML element there is a corresponding React component, ready to be used.
We can think of JSX
as an intermediate syntax that will be converted to javascript, just as SASS is an intermediate syntax for CSS.
What actually converts JSX into javascript is Babel, the javascript compiler included at create-react-app
.
The same code converted to javascript would look like this:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
return React.createElement(
'div',
{ className: 'App' },
React.createElement('div', { className: 'App-header' },
React.createElement('img', { src: logo, className: 'App-logo', alt: 'logo' }),
React.createElement('h2', null, 'Welcome to React')
),
React.createElement('p', { className: 'App-intro' },
'To get started, edit ',
React.createElement('code', null, 'src/App.js'),
' and save to reload.'
)
);
}
}
export default App;
Before we begin our implementation, just a little more theory. See the signature of the method createElement
:
React.createElement(type, [props], [...children])
Then, on the following call:
React.createElement('img', {src: logo, className: 'App-logo', alt: 'logo'})
img
is the element type. For each HTML element there is a corresponding React component.{src: logo, className: 'App-logo', alt: 'logo'}
are theprops
of that component. We can think ofprops
as the arguments of a function. And we can think of React components as functions, which receiveprops
and return a React element.- Note that React components that represent HTML tags, as in the example above, often use
props
matching HTML attribute names. ExceptclassName
, which was used instead ofclass
because this is a javascript key word.
Implementing the first component
Since we are building a news feed, nothing better than starting to implement this component. The implementation is very simple:
class Feed extends Component {
render() {
return (
<div className="feed">
<div className="post">
<span>This is my first post!</span>
</div>
</div>
)
}
}
Let’s display our Feed
at the render
of the root component App
:
class App extends Component {
render() {
return (
<div className="App">
<Feed />
</div>
);
}
}
Extracting the component Post
As you may have guessed, you can extract a component Post
from within the component Feed
. This is one of the main ideas of React, to compose a large component from several small components:
class Feed extends Component {
render() {
return (
<div className="feed">
<Post content="This is my first post!" />
<Post content="This is my second post!" />
</div>
)
}
}
See that we added a little more dynamism here. We pass the text of each post through a prop
of our component Post
called content
. The implementation of the component Post
looks like this:
class Post extends Component {
render() {
return (
<div className="post">
<span>{this.props.content}</span>
</div>
)
}
}
Using the state
Instead of creating a component Post
for each news item, let’s create a news list and then display via javascript a component for each element in that list.
Difference between state
and props
We will use the state
for this. Unlike the immutable way of props
, the state
is changeable. Since we want this list of news to grow over time, it makes sense to store this list in the state
of our component Feed
.
We initialize the state
in the constructor of our class, like this:
class Feed extends Component {
constructor(props) {
super(props);
this.state = {
posts: [
{content: 'This is my first post!'},
{content: 'This is my second post!'}
]
}
}
render() {
const posts = this.state.posts.map((post, index) =>
<Post key={index} value={post} />
);
return (
<div className="feed">
{posts}
</div>
)
}
}
In the method render
we map each object in our list to an element Post
and then we assign that list of components to the constant posts
.
We display this list of components Post
as children of Feed
’s root component.
Note that method
render
must return a React element. Therefore we need a componentdiv
involving our list of posts.
Extra Reading:
Form to create news
Let’s create the component PostForm
. This component will display a form to create news.
Let’s also create our first method. So far we have only implemented the render
of each component. This method will be named handleSubmit
, and as the name implies, it will be called when the user submits the form.
Our initial version of the component PostForm
just connects the method handleSubmit
to the form’s event onSubmit
.
The method handleSubmit
still needs a way to get the text field’s value and a way to send that data to the component Feed
:
class PostForm extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
}
render() {
return (
<div className="post-form">
<form onSubmit={this.handleSubmit}>
<label>
Content:
<input type="text" />
</label>
<button className="button">Submit</button>
</form>
</div>
)
}
}
Let’s understand some points of this implementation.
Since we are using ES6 classes to implement our components, we need one additional step to declare our methods.
At line 59 we bind
our method handleSumbit
to make it visible from this
.
At line 72 we have our input
. In React we can implement form fields in two ways: controlled components and uncontrolled components.
Controlled components vs Uncontrolled components
- Controlled components: more React-way, because field’s value is held in the
state
of a React component; - Uncontrolled components: simpler than the controlled components. The value is held in the DOM itself.
In this example we use an uncontrolled component. Uncontrolled components are recommended for simple forms, such as the form of our example.
Implementing an uncontrolled component
As in our uncontrolled component the field value is held in the DOM itself, we need a way to capture the value of that input. This is a great opportunity to use a ref
from React.
We make two changes to the code below:
- In the code
ref={(input) => this.content = input}
, at line 73, we make our text field available inthis
through the attributecontent
. - In
handleSumbit
we usethis.content
to access our uncontrolled component. The only thing we do inhandleSubmit
for now is clear the value of our text field.
class PostForm extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
this.content.value = '';
event.preventDefault();
}
render() {
return (
<div className="post-form">
<form onSubmit={this.handleSubmit}>
<label>
Content:
<input type="text" ref={(input) => this.content = input} />
</label>
<button className="button">Submit</button>
</form>
</div>
)
}
}
Extra reading:
Communication between components
We need a way to send the values submitted by PostForm
’s form to the component Feed
.
Using a prop
to pass a method to another component
The trick here is to implement in Feed
a method that knows how to add a news item in the list and then pass that method to the component PostForm
through a prop
.
It’s easier to understand by seeing the code, so here we go:
class Feed extends Component {
constructor(props) {
super(props);
this.state = {
posts: [
{content: 'This is my first post!'},
{content: 'This is my second post!'}
]
}
this.handleNewPost = this.handleNewPost.bind(this);
}
handleNewPost(post) {
this.setState({
posts: this.state.posts.concat([post])
});
}
render() {
const posts = this.state.posts.map((post, index) =>
<Post key={index} value={post} />
);
return (
<div className="feed">
{posts}
<PostForm onSubmit={this.handleNewPost} />
</div>
)
}
}
Here’s what we changed:
- We implemented the method
handleNewPost
(and made it visible tothis
with thebind
of line 24); - We displayed the component
PostForm
in line 40; - And we passed the method
handleNewPost
to the componentPostForm
through aprop
namedonSubmit
, at line 40.
The big trick was to pass the method through a prop
.
Extra reading:
Updating state
The implementation of handleNewPost
requires a special attention: in React every state update must be done using the method setState
. The method setState
receives a hash with the attributes that should be updated.
To be clear: Never change state
directly!
With the call to setState
, React knows that component’s state has changed, and that it’s time to call render
again to reflect these changes visually.
Reconciliation: How React Updates the DOM
Recapping: when the user writes a news in PostForm
and submits it, the method handleSubmit
of PostForm
is called. The method handleSubmit
, in turn, calls the method handleNewPost
of Feed
through the prop onSubmit
of the PostForm
.
In handleNewPost
a state update is done. At that moment React calls the Feed
’s method render
in order to show the news feed updated on the screen.
React, however, does not “reload” the entire news feed when only one news item is added. Operations that manipulate the DOM are expensive. So to improve performance, React keeps an internal representation of the UI, known as “Virtual DOM”.
When Feed
’s state
is updated and method render
is called again, React compares the current node tree with the new node tree in the Virtual DOM through a diffing algorithm.
For our example, the React diffing algorithm realizes that it’s not necessary to recreate the whole Feed
, just one Post
element must be added in the actual DOM.
This process is called Reconciliation.
Extra reading:
Calling a method through a prop
To complete the communication between the components we only need to use the prop
passed by Feed
to PostForm
:
class PostForm extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
this.props.onSubmit({content: this.content.value})
this.content.value = '';
event.preventDefault();
}
Adding Categories to News
Every news in our feed belongs to a category.
Let’s start with a pre-determined list of categories: World, Business, Tech, and Sport. We create then a constant to represent these values:
const categories = ['World', 'Business', 'Tech', 'Sport'];
And then we update the component Feed
by adding the property category
to the initial list of news in our state
:
class Feed extends Component {
constructor(props) {
super(props);
this.state = {
posts: [
{category: categories[0], content: 'This is my first post!'},
{category: categories[1], content: 'This is my second post!'}
]
}
In the component Post
we display the category:
class Post extends Component {
render() {
return (
<div className="post">
<span className="label">{this.props.value.category}</span>
<span className="content">{this.props.value.content}</span>
</div>
)
}
}
And in the component PostForm
we make two changes: we add a field to the category and update the method handleSubmit
to send the selected category:
class PostForm extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
this.props.onSubmit({
category: this.category.value,
content: this.content.value
});
this.category.value = categories[0];
this.content.value = '';
event.preventDefault();
}
render() {
return (
<div className="post-form">
<form onSubmit={this.handleSubmit}>
<label>
Category:
<select ref={(input) => this.category = input}>
{categories.map((category, index) =>
<option key={category} value={category}>{category}</option>
)}
</select>
</label>
<label>
Content:
<input type="text" ref={(input) => this.content = input} />
</label>
<button className="button">Submit</button>
</form>
</div>
)
}
}
Filtering news
News should be filtered by category or by content. Let’s then add a text field at the top of Feed
to implement our filter.
Unlike the text field used in PostForm
, we will use a Controlled Component here. This controlled input will be implemented in the component Filter
.
Before creating the component Filter
, let’s implement the logic of filtering news in Feed
.
Logic of filtering news
We will make the following changes:
- Add
filteredPosts
tostate
. - In the method
render
, we will display theposts
complete list only if the listfilteredPosts
is empty. Otherwise, we will displayfilteredPosts
. - The logic of filtering the posts based on the value entered by the user in
Filter
will be implemented in the methodhandleFilter
. - The communication between the components
Feed
andFilter
will follow the same strategy used previously. We will pass the methodhandleFilter
as aprop
ofFilter
.
The implementation is following:
class Feed extends Component {
constructor(props) {
super(props);
this.state = {
posts: [
{category: categories[0], content: 'This is my first post!'},
{category: categories[1], content: 'This is my second post!'}
],
filteredPosts: []
}
this.handleNewPost = this.handleNewPost.bind(this);
this.handleFilter = this.handleFilter.bind(this);
}
handleNewPost(post) {
this.setState({
posts: this.state.posts.concat([post])
});
}
handleFilter(filter) {
this.setState({
filteredPosts: this.state.posts.filter((post) =>
post.category.toUpperCase() === filter.toUpperCase() ||
post.content.includes(filter)
)
});
}
render() {
const posts = this.state.posts.map((post, index) =>
<Post key={index} value={post} />
);
const filteredPosts = this.state.filteredPosts.map((post, index) =>
<Post key={index} value={post} />
);
return (
<div className="feed">
<Filter onFilter={this.handleFilter} />
{filteredPosts.length > 0 ? filteredPosts : posts}
<PostForm onSubmit={this.handleNewPost} />
</div>
)
}
}
Extra reading:
Implementation of a controlled component: the component Filter
In any scenario a little more complex it’s recommended to use a controlled component. As you already know, controlled components store the value of the field in state
, as any React component would do.
The implementation of a controlled component follows this roadmap: we create a method handleChange
that updates the value of the input at state
on every change in the field, as defined in onChange={this.handleChange}
.
In order to apply the filter, the user must enter the name of a category or a piece of content and press Enter. Then we also implemented the method handleKeyUp
.
The initial version of the component Filter
is following:
class Filter extends Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleKeyUp = this.handleKeyUp.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleKeyUp(event) {
if (event.key === 'Enter') {
this.props.onFilter(this.state.value);
}
}
render() {
return (
<div>
<label>
<input type="search" value={this.state.value}
onChange={this.handleChange}
onKeyUp={this.handleKeyUp}
placeholder="Filter by category or content..." />
</label>
</div>
)
}
}
Extra reading:
Removing the filter
We want to clear the previously applied filter by leaving the text field empty (pressing delete
to erase all characters, for example).
To implement this strategy, we call onFilter
when the value of the field is empty at handleChange
:
class Filter extends Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleKeyUp = this.handleKeyUp.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
if (event.target.value === '') {
this.props.onFilter('');
}
}
Saving the news list locally
Our implementation is almost done! To finish it, let’s save the news list on localStorage
. That way we do not lose our changes when closing the browser.
We have modified only the state
initialization and the method handleNewPost
:
class Feed extends Component {
constructor(props) {
super(props);
this.state = {
posts: JSON.parse(localStorage.getItem('posts')) || [],
filteredPosts: []
}
this.handleNewPost = this.handleNewPost.bind(this);
this.handleFilter = this.handleFilter.bind(this);
}
handleNewPost(post) {
var posts = this.state.posts.concat([post]);
this.setState({posts: posts});
localStorage.setItem('posts', JSON.stringify(posts));
}
Next steps
Our news feed is still not very useful. It only shows news created by the user. To make it really interesting, we have to implement the server side.
And that’s what we’ll do in the next blog post. We’ll create an application in Ruby on Rails that will communicate with our news feed.