CodeSchool Course - Powering Up With React
Level 1. First Component
What is React
React is a JavaScript library for building user interfaces (UIs). Some people use it as the V in MVC.
Why React
React was build to solve one problem: building large applications with data that changes over time.
Conceived at Facebook
Heavily used on products made by Facebook and Instagram. Built to simplify the process of building complex UIs.
After Facebook open-sourced React, it’s now used by Dropbox, AirBNB, Instagram, Netflix, and Paypal.
Prerequisites
Javascript Basics
- Declaring variables
- Creating and invoking functions
ES2015
See ES2015
- Class Syntax
- Arrow functions
- Spread operator
What We’ll Learn
We’ll cover some of the features React offers, including how to:
- Write React components
- Render data to the page
- Make components communicate
- Handle user events
- Capture user input
- Talk to remote servers
Component-based Architecture
In React, we solve problems by creating components. If a component gets too complex, we break it into smaller, simpler components.
We’ll first focus on a comment section added to a page, which features a simple form where a person can enter their name and comment, then click on the ‘Post Comment’ button. Below this is a list of comments. This entire interface is stored inside of a “StoryBox” component.
Inside of the StoryBox component is a StoryForm component that contains the form hat users can use to add stories to the feed, as well as a separate Story component for each story displayed in the feed.
What is a React Component
A component in React works similar to JavaScript functions: It generates an output every time it is invoked.
With a React component a render() method is called, which generates the HTML:
<div>
<p>Good Morning</p>
<p>10:45AM</p>
</div>
10 minutes later we run the render() method again, and instead it generates:
<div>
<p>Good Morning</p>
<p>10:55AM</p>
</div>
The Virtual DOM Explained
The virtual DOM is an in-memory representation of real DOM elements generated by React components before any changes are made to the page.
When the component is rendered, the HTML that is being generated is the Virtual DOM. The output is then transformed into actual HTML within the browsers DOM.
The Virtual DOM in Action
Why go through the extra step? Because using the Virtual DOM makes the updates to the actual DOM faster.
Virtual DOM diffing allows React to minimize changes to the DOM as a result of user actions — therefore, increasing browser performance.
With the example shown above, the second time the component renders the HTML, the time has changed. The diffing makes the update faster.
Creating Our First React Application
We want to simply print a message to the screen using a React component.
Components in React are JavaScript classes that inherit from the React.Component base class.
/* components.js */
// Components are written in upper camel case
// Component class inherits from a React base class
class StoryBox extends React.Component {
// every component needs a render() function
render() {
return <div>Story Box</div>
}
}
You don’t have to put quotes around the markup that is being returned, because of JSX. It allows us to include HTML in our JavaScript.
Now we need to tell our application where to put the result into our web page.
Rendering Our First React Component
We use ReactDOM to render components to our HTML page as it reads output from a supplied React component and adds it to the DOM.
class StoryBox extends React.Component {
render() {
return <div>Story Box</div>
}
}
// first arg: Invokes the StoryBox component (no quotes needed)
// second arg: The target container where component will be rendered to
ReactDOM.render(<StoryBox />, document.getElementById("story-app"))
Referencing the Component
Every time we create a new React component, we use it by writing an element named after the class.
class StoryBox
→ <StoryBox />
This is JSX syntax, so the case used with the component name is important.
Application Structure
/* components.js */
ReactDOM.render(<StoryBox />, document.getElementById("story-app"))
The page must contain a DIV with the correct ID.
<!-- index.html -->
<!DOCTYPE html>
<html>
<body>
<div id="story-app"></div>
</body>
</html>
That’s all there is to creating a component. Now we just need to add libraries.
<!-- index.html -->
<!DOCTYPE html>
<html>
<body>
<div id="story-app"></div>
<!-- Supports React Components -->
<script src="vendors/react.js"></script>
<script src="vendors/react-dom.js"></script>
<!-- Provides support for ES2015 and JSX code -->
<script src="vendors/babel.js"></script>
<script type="text/babel" src="components.js"></script>
</body>
</html>
Project Folder
- index.html
- components.js
- vendors
- react.js
- react-dom.js
- babel.js
Our React Application Flow
To clarify, here is what takes place when we load a page with a React component:
- index.html is opened
- dependencies defined in index.html are loaded
- StoryBox component is rendered, then applied to the actual DOM element
Quick Recap on React
- React was built to solve one problem: building large applications with data that changes over time
- In React, we write apps in terms of components
- We use JavaScript classes when declaring React components
- Components must extend the React.Component class and must contain a render() method
- We call the ReactDOM.render() function to render components to a webpage
Level 1 - Section 2
No Quotes Around Markup
The markup we use when writing React apps is not a string. This markup is called JSX (JavaScript XML).
class StoryBox extends React.Component {
render() {
// HTML elements are written in lowercase
return <div>Story Box</div>
}
}
ReactDOM.render(
// React components are written in upper camelcase
<StoryBox />,
document.getElementById("story-app")
)
JSX is just another way of writing JavaScript with a transpile step.
// JSX
<div>Story Box</div>
// Transpiled JSX Code
React.createElement('div', null, 'Story Box')
// JSX
<StoryBox />
// Transpiled JSX Code
React.createElement(StoryBox, null)
This may take some getting used to, but will feel natural after gaining confidence in using it.
Getting Used to the JSX Syntax
JSX looks similar to HTML, and it is ultimately transformed into JavaScript.
class StoryBox extends React.Component {
render() {
return (
<div>
<h3>Stories App</h3>
<p className="lead">Sample paragraph</p>
</div>
)
}
}
Notice above how the attribute on the paragraph is ‘className’ instead of ‘class’. This is because ‘class’ is a reserved JavaScript keyword.
// Transpiled JSX code
React.createElement(
"div",
null,
React.createElement("h3", null, "Stories App"),
React.createElement("p", { className: "lead" }, "Sample paragraph")
)
Browsers do not understand JSX, but they do understand JavaScript. They are able to run the transpiled JavaScript that is created from the JSX, which then is applied as HTML within the DOM.
<div data-reactroot>
<div>
<h3>Stories App</h3>
<p class="lead">Sample paragraph</p>
</div>
</div>
Using the Date Object in JSX
Here, we’re displaying the current time using JavaScript’s native Date object and JSX.
class StoryBox extends React.Component {
render() {
const now = new Date()
return (
<div>
<h3>Stories</h3>
<p className="lead">Current time: {now.toTimeString()}</p>
</div>
)
}
}
Code written within curly braces gets interpreted as literal JavaScript in JSX.
Iterating Arrays in JSX
Here, we’re displaying a list of elements using JSX and JavaScript’s native map function.
class StoryBox extends React.Component {
render() {
// ...
const topicsList = ["HTML", "JavaScript", "React"]
return (
<div>
<ul>
{topicsList.map(topic => (
<li>{topic}</li>
))}
</ul>
</div>
)
}
}
In the above code, the JSX is converted to:
<li>HTML</li>
<li>JavaScript</li>
<li>React</li>
Quick Recap on JSX
- JSX stands for JavaScript XML.
- JSX markup looks similar to HTML, but ultimately gets transpiled to JavaScript function calls, which React will know hot to render to the page.
- Code written within curly braces are interpreted as literal JavaScript
- It is a common pattern to map arrays to JSX elements.
Level 2 - Talk Through Props
The App We’re Building
We are building a commenting engine that will allow visitors to post comments on a blog post, picture, video, etc. This will allow users to interact with each other, provide social commentary, etc.
Adding Components to Our Comments App
What the structure of our React app should look like.
- CommentBox as the root component
- Comment as the re-usable component for each comment displayed
Pattern for Adding New Components
There are some common things we always do when creating new components.
// New class inherits from React.Component
class NewComponent extends React.Component {
render() {
// render method must return JSX
return ( ... );
}
}
Coding the Comment List
Let’s start with an HTML mockup and identify potential components by looking at the markup.
Here is a mockup of the comment box HTML:
<div class="comment-box">
<h3>Comments</h3>
<h4>class="comment-count">2 comments</h4>
<div class="comment-list">
<!-- each comment goes here -->
</div>
</div>
Here is an isolated example of what the comment component will render:
<div class="comment">
<p class="comment-header">Anne Droid</p>
<p class="comment-body">
I wanna know what love is...
</p>
<div class="comment-footer">
<a href="#" class="comment-footer-delete">
Delete Comment
</a>
</div>
</div>
Writing the Comment Component
The Comment component renders the markup for each comment, including its author and body.
class Comment extends React.Component {
render() {
return (
<div className="comment">
<p className="comment-header">Anne Droid</p>
<p className="comment-body">I wanna know what love is...</p>
<div className="comment-footer">
<a href="#" className="comment-footer-delete">
Delete Comment
</a>
</div>
</div>
)
}
}
We can now reference this component in JSX as <Comment />
.
Writing the CommentBox Component
Now we’ll declare the CommentBox component and use the previously declared Comment component.
class CommentBox extends React.Component {
render() {
return (
<div className="comment-box">
<h3>Comments</h3>
<h4> className="comment-count">2 comments</h4>
<div className="comment-list">
<Comment />
<Comment />
</div>
</div>
)
}
}
As you can see here, we’re using the Comment component twice. The only problem here is that all our comments look the same.
React Components Accept Arguments
Arguments passed to components are called props. They look similar to regular HTML element attributes.
class CommentBox extends React.Component {
render() {
return (
<div className="comment-box">
<h3>Comments</h3>
<h4> className="comment-count">2 comments</h4>
<div className="comment-list">
<Comment author="Morgan McCircuit" body="Great picture!" />
<Comment author="Bending Bender" body="Excellent stuff" />
</div>
</div>
)
}
}
Reading Props in the Comment Component
Arguments passed to components can be accessed using the this.props object.
class Comment extends React.Component {
render() {
return (
<div className="comment">
<p className="comment-header">{this.props.author}</p>
<p className="comment-body">{this.props.body}</p>
<div className="comment-footer">
<a href="#" className="comment-footer-delete">
Delete Comment
</a>
</div>
</div>
)
}
}
Quick Recap on Props
We just covered a lot of content — here’s a summary of what we learned.
- Convert HTML mockup to React components
- Created two components: CommentBox and Comment
- How to pass arguments to components using props
- Props look like HTML element attributes
Problem: Props Aren’t Dynamic Yet
We are passing literal strings as props, but what if we wanted to traverse an array of objects? In the real world we rarely work with hardcoded values.
JavaScript Object Arrays
Typically, when we consume data from API servers, we are returned object arrays.
const commentList = [
{ id: 1, author: "Morgan McCircuit", body: "Great picture!" },
{ id: 2, author: "Bending Bender", body: "Excellent stuff" }
]
Mapping an Array to JSX
We can use JavaScript’s map function to create an array with Comment components.
class CommentBox extends React.Component {
// ...
// Underscore helps distinguish custom methods from React methods
_getComments() {
const commentList = [
{ id: 1, author: "Morgan McCircuit", body: "Great picture!" },
{ id: 2, author: "Bending Bender", body: "Excellent stuff" }
]
return commentList.map(() => {
return <Comment />
})
}
}
Passing Dynamic Props
The callback to map takes an argument that represents each element from the calling object.
class CommentBox extends React.Component {
// ...
_getComments() {
const commentList = [
{ id: 1, author: "Morgan McCircuit", body: "Great picture!" },
{ id: 2, author: "Bending Bender", body: "Excellent stuff" }
]
return commentList.map(comment => {
return <Comment author={comment.author} body={comment.body} />
})
}
}
Anything in curly braces is interpreted as literal JavaScript.
<img src={this.props.avatarUrl} alt={`${this.props.author}'s picture`} />
Using Unique Keys on List of Components
Specifying a unique key when creating multiple components of the same type can help improve performance. It helps React track which element is which within the loop.
<Comment author={comment.author} body={comment.body} key={comment.id} />
Using the _getComments() method
We’ll store the returned value in a variable named comments and use it for display purposes.
class CommentBox extends React.Component {
render() {
const comments = this._getComments();
return(
<div className="comment-box">
<h3>Comments</h3>
<h4 className="comment-count">{comments.length} comments</h4>
<div className="comment-list">
// JSX knows how to render arrays of components
{comments}
</div>
</div>
);
}
_getComments() { ... }
}
Incorrect Grammar on the Comments Title
The title has incorrect grammar in some cases. When title says ‘3 comments’ or ‘2 comments’, it’s fine, but when it says ‘1 comments’ it’s incorrect grammar.
Fixing the Title With Comment Count
Let’s write a new method called _getCommentsTitle() that handles the plural case in our title.
class CommentBox extends React.Component {
render() {
const comments = this._getComments()
return (
// ...
<h4 className="comment-count">
{this._getCommentsTitle(comments.length)}
</h4>
// ...
)
}
_getCommentsTitle(commentCount) {
if (commentCount === 0) {
return "No comments yet"
} else if (commentCount === 1) {
return "1 comment"
} else {
return `${commentCount} comments`
}
}
}
The title now handles different quantities of comments accordingly.
Quick Recap on Dynamic Props
- How to pass dynamic props using variables
- How to map object arrays to JSX arrays for display purposes
- Used JavaScript to handle plural case on the title
Level 3 - Component State
Show and Hide Comments
We’d like to add a button to the page that will let users toggle the comments. At top of CommentBox it displays the number of comments: “3 Comments”, and also will have a ‘Show Comments’ button. The comments are hidden until this button is clicked on.
Once the comments are displayed, the button changes to ‘Hide Comments’. How can we show and hide comments based on button clicks?
Different Ways to Manipulate the DOM
- Direct DOM Manipulation
- jQuery, Backbone, etc.
- Indirect DOM Manipulation
- React
Direct DOM Manipulation
One way to manipulate the DOM API is by modifying it directly via JavaScript in response to browser events.
Events → DOM Updates
/* jquery example */
$(".show-btn").on("click", function() {
$(".comment-list").show()
})
$(".hide-btn").on("click", function() {
$(".comment-list").hide()
})
Indirect DOM Manipulation
In React, we don’t modify the DOM directly. Instead, we modify a component state object in response to user events and let React handle updates to the DOM.
Events → Update State → DOM Updates
We modify the component state, and then let React handle the updates.
render() {
if (this.state.showComments) {
// code displaying comments
} else {
// code hiding comments
}
}
How to Use State in a Component
The state is a JavaScript object that lives inside each component. We can access it via this.state.
class CommentBox extends React.Component {
render() {
const comments = this._getComments()
if (this.state.showComments) {
// add code for displaying comments
}
return (
<div className="comment-box">
<h4 className="h4">{this._getCommentsTitle(comment.length)}</h4>
<div className="comment-list">{comments}</div>
</div>
)
}
}
Showing Comments Only if State Is true
class CommentBox extends React.Component {
render() {
const comments = this._getComments()
let commentNodes
if (this.state.showComments) {
commentNodes = <div className="comment-list">{comments}</div>
}
return (
<div className="comment-box">
<h4 className="h4">{this._getCommentsTitle(comment.length)}</h4>
{commentNodes}
</div>
)
}
}
Hiding Comments on the Initial State
We set the initial state of our component in the class constructor.
class CommentBox extends React.Component {
constructor() {
super()
this.state = {
showComments: false
}
}
render() {
// ...
}
}
When defining a constructor, super() must be called to ensure that the React.Component constructor behavior is kept intact.
How to Update a Component’s State
We don’t assign to the state object directly — instead, we call setState by passing it an object.
// wrong, will not work
this.state.showComments = true
// updates showComments property
this.setState({ showComments: true })
Calling setState will only update the properties passed as an argument, not replace the entire state object.
Causing State Change
State changes are usually triggered by user interactions with our app.
Things that could cause state change:
- Button clicks
- Link clicks
- Form submissions
- AJAX requests
- And more!
Handling Click Events
Let’s add a button that will toggle the showComments state when a click event is fired.
class CommentBox extends React.Component {
render() {
// ...
return(
...
<button onClick={this._handleClick.bind(this)}>Show comments</button>
...
);
}
_handleClick() {
this.setState({
showComments: !this.state.showComments
});
}
}
Button Text Logic Based on State
We can switch the button text based on the component’s state.
class CommentBox extends React.Component {
render() {
// ...
let buttonText = "Show comments"
if (this.state.showComments) {
buttonText = "Hide comments"
// ...
}
return (
// ...
<button onClick={this._handleClick.bind(this)}>{buttonText}</button>
// ...
)
}
}
Demo: Hide and Show Comments
Our app shows and hides comments when the button is clicked.
Quick Recap on State
- State represents data that changes over time.
- We declare and initial state in the component’s constructor.
- We update state by calling this.setState().
- Calling this.setState() causes our component to re-render.
Level 4 - Synthetic Events
Adding New Comments
We want to let users add new comments to our app. We will call the new component CommentForm, and it will provide input fields for the user to provide their name, the comment text, and then click on ‘Post Comment’.
New Component: CommentForm
CommentForm is a new component that will allow users to add comments to our app. It will be a child of the CommentBox, displayed above the list of Comments.
Coding the CommentForm Component
class CommentForm extends React.Component {
render() {
return (
<form className="comment-form">
<label>Join the discussion</label>
<div className="comment-form-fields">
<input placeholder="Name:" />
<textarea placeholder="Comment:" />
</div>
<div className="comment-form-actions">
<button type="submit">Post comment</button>
</div>
</form>
)
}
}
Adding an Event Listener to Our Form
To add an event listener to the form, we use the onSubmit prop and pass a handler to it.
class CommentForm extends React.Component {
render() {
return (
<form className="comment-form" onSubmit={this._handleSubmit.bind(this)}>
// ...
<input placeholder="Name:" />
<textarea placeholder="Comment:" />
// ...
</form>
)
}
_handleSubmit(event) {
// prevents page from reloading when form is submitted
event.preventDefault()
}
}
Problem: Can’t Access User Input in handleSubmit()
We still need a way to access the name and comment field values within the _handleSubmit() function.
Accessing Form Data from Handler
We can use refs for assign form values to properties on the component object.
<input placeholder="Name:" ref={(input) => this._author = input} />
<textarea placeholder="Comment:" ref={(textarea) => this._body = textarea}></textarea>
We’ll use these refs to access values from the input elements.
class CommentForm extends React.Component {
render() {
return (
<form className="comment-form" onSubmit={this._handleSubmit.bind(this)}>
// ...
<input placeholder="Name:" ref={input => (this._author = input)} />
<textarea
placeholder="Comment:"
ref={textarea => (this._body = textarea)}
/>
// ...
</form>
)
}
_handleSubmit(event) {
// prevents page from reloading when form is submitted
event.preventDefault()
}
}
What Setting the refs is Actually Doing
<input placeholder="Name:" ref={input => (this._author = input)} />
This is the same as:
<input
placeholder="Name:"
ref={function(input) {
this._author = input
}.bind(this)}
/>
The DOM element itself is passed into the callback as ‘input’, with the
CommentForm passed as *this*
via the
bind() call.
You may be wondering, who calls this function? React runs ref callbacks on render.
Passing the User Input to the CommentBox
class CommentForm extends React.Component {
render() {
return (
// ...
<input placeholder="Name:" ref={(input) => this._author = input} />
<textarea placeholder="Comment:" ref={(textarea) => this._body = textarea}></textarea>
// ...
);
}
_handleSubmit(event) {
event.preventDefault();
// these are populated from refs in JSX
let author = this._author;
let body = this._body;
// this method will be passed as an argument from the parent CommentBox
this.props.addComment(author.value, body.value);
}
}
Data About Comments Lives in CommentBox
This is a common pattern with React, where we have to pass references to child components. The array of comments is part of the CommentBox component, so we need to propagate new comments from CommentForm over to CommentBox.
Propagating data about a new comment to CommentBox is simple. You just pass it a callback prop.
Using CommentForm to Add Comments
Functions in JavaScript are first-class citizens, so we can pass them as props to other components.
class CommentBox extends React.Component {
render() {
return (
<div className="comment-box">
<CommentForm addComment={this._addComment.bind(this)} />
// ...
</div>
)
}
// this method gets triggered by CommentForm when a new comment is added
_addComment(author, body) {}
}
Adding Functionality to Post Comments
class CommentBox extends React.Component {
render() {
return (
<div className="comment-box">
<CommentForm addComment={this._addComment.bind(this)} />
// ...
</div>
)
}
_addComment(author, body) {
const comment = {
id: this.state.comments.length + 1,
author,
body
}
this.setState({ comments: this.state.comments.concat([comment]) })
}
}
We are using concat() instead of push(), because concat() returns a new reference to the array, instead of mutating the existing array. This helps React stay fast, by detecting the change that happened in the array earlier on.
This comments
array doesn’t exist in the state yet though.
Comments Are Not Part of the State
Currently, we’re defining an array every time the _getComments method is called. Let’s move this data to the state.
class CommentBox extends React.Component {
// ...
_getComments() {
const commentList = [
{ id: 1, author: "Morgan McCircuit", body: "Great picture!" },
{ id: 2, author: "Bending Bender", body: "Excellent stuff" }
]
// ...
}
}
To dynamically update the component, we need to move the comments list into the components state.
class CommentBox extends React.Component {
constructor() {
super()
this.state = {
showComments: false,
comments: [
{ id: 1, author: "Morgan McCircuit", body: "Great picture!" },
{ id: 2, author: "Bending Bender", body: "Excellent stuff" }
]
}
}
}
Now they are part of the component state.
Rendering Comments From the State
Let’s use the comments from the state object to render our component.
class CommentBox extends React.Component {
// ...
_getComments() {
return this.state.comments.map(comment => {
return (
<Comment author={comment.author} body={comment.body} key={comment.id} />
)
})
}
}
Review: Event Handling in React
In order to ensure events have consistent properties across different browsers, React wraps the browser’s native events into synthetic events, consolidating browser behaviors into one API.
Form submission handling might work slightly different for each browser, but React provides support for the ‘onSubmit’ event.
Quick Recap
- We use React’s event system to capture user input, including form submissions and button clicks.
- Refs allow us to reference DOM elements in our code after the component has been rendered.
- Parent components can pass callback functions as props to child components to allow two-way communication.
- Synthetic events are a cross-browser wrapper around a browser’s native event system.
Level 5 - Section 1 - Talking to Remote Servers
5.1 Using Lifecycle Methods to Load Comments
Comments Are Static
In the real world, we’d want to pull comments from an API instead of hard-coding the data.
class CommentBox extends React.Component {
constructor() {
super()
this.state = {
showComments: false,
comments: [
{ id: 1, author: "Morgan McCircuit", body: "Great picture!" },
{ id: 2, author: "Bending Bender", body: "Excellent stuff" }
]
}
}
}
Loading Comments From a Remote Server
Let’s set the initial state of comments as an empty array so we can later populate it with data from an API server.
class CommentBox extends React.Component {
constructor() {
super()
this.state = {
showComments: false,
comments: []
}
}
}
Adding jQuery as a Dependency
jQuery will help us make Ajax requests. We can download it from the jQuery website and include it in our HTML page.
- index.html
- components.js
- vendors
- react.js
- react-dom.js
- babel.js
- jquery.js
<!DOCTYPE html>
<html>
<body>
<div id="story-app"></div>
<script src="vendors/react.js"></script>
<script src="vendors/react-dom.js"></script>
<script src="vendors/jquery.js"></script>
<script src="vendors/babel.js"></script>
<script type="text/babel" src="vendors/components.js"></script>
</body>
</html>
How to Fetch Data in a Component
Let’s write a class method that will make Ajax requests in the CommentBox component.
class CommentBox extends React.Component {
// ...
_fetchComments() {
jQuery.ajax({
method: "GET",
url: "/api/comments",
success: comments => {
this.setState({ comments })
}
})
}
}
We call the setState method when data is received from the API server. We are using the arrow function because it preserves the ‘this’ binding to our class.
Deciding Where to Call _fetchComments()
class CommentBox extends React.Component {
render() {
// ...
}
_fetchComments() {
// ...
}
}
We cannot call _fetchComments from render(), or else we will get an infinite oop, because the render() method is used by React when new data must be shown inside of the component rendering. fetchComments calls setState, which calls render().
React’s Lifecycle Methods
Lifecycle methods in React are function that get called while the component is rendered for the first time or about to be removed from the DOM.
We will focus on 3 lifecycle methods.
- componentWillMount() - called after constructor()
- componentDidMount() - called after render()
- componentWillUnmount()
For a full list of React’s lifecycle methods, visit React Component - The Component Lifecycle
In React, mounting means rendering for the first time. Unmounting means getting removed from the DOM.
Fetching Data on the Mounting Phase
The componentWillMount method is called before the component is rendered to the page.
class CommentBox extends React.Component {
componentWillMount() {
_fetchComments()
}
render() {
// ...
}
// ...
}
Getting Periodic Updates
In order to check whether new comments are added, we can periodically check the server for updates. This is known as polling.
Polling Data on the Mounting Phase
The componentDidMount method is called after the component is rendered to the page. This is a perfect place to start our polling process.
class CommentBox extends React.Component {
// ...
componentDidMount() {
// run comment fetching every 5000 milliseconds (5 seconds)
setInterval(() => this._fetchComments(), 5000)
}
}
Updating Component With New Comments
React optimizes the rendering process by only updating the DOM when changes are detected on the resulting markup. When running setState, if the actual state in the Virtual DOM is not modified, no changes occur to the actual page.
- New state value after initial Ajax request → DOM change happens
- No new state value after second periodic Ajax request → No DOM change
- New state value after third periodic Ajax request → DOM change happens
Note: render() is called after each Ajax response because setState is in the response function.
Memory Leaks on Page Change
Page changes in a single-page app environment will cause each CommentBox component to keep loading new comments every five seconds, even when they’re no longer being displayed.
With each new view that is loaded in a single page application, without the browser actually reloading the rendered page, the setInterval() method sets up yet another interval timer that makes the same request every 5 seconds.
Preventing Memory Leaks
Each component is responsible for removing any timers it has created. We will remove the timer on the componentWillUnmount method.
class CommentBox extends React.Component {
// ...
componentDidMount() {
this._timer = setInterval(() => this._fetchComments(), 5000)
}
componentWillUnmount() {
clearInterval(this._timer)
}
}
This will ensure that the timer is removed when the component is about to be removed from the DOM.
Memory Leak is Gone
Our app can be freely navigated through now, without causing multiple unnecessary calls to the API.
Reviewing the Steps for Loading Comments
- componentWillMount() is called.
- render() is called and CommentBox is mounted. “No comments yet” displayed.
- Component waits for API response and when it is received, setState() is called, causing render() to be called again.
- componentDidMount() is called, causing this._fetchComments to be triggered every five seconds.
- componentWillUnmount() is called when the component is about to be removed from the DOM and clears the fetchComments timeout.
Quick Recap on Lifecycle Methods
Lifecycle methods in React are functions that get called during certain phases that components go through.
- componentWillMount() is called before the component is rendered.
- componentDidMount() is called after the component is rendered.
- componentWillUnmount() is called immediately before the component is removed from the DOM.
Level 5 - Section 2 - Adding and Deleting Comments on the Server Side
Deleting Comments
Our comments have a Delete Comment button now, but no delete actions are associated to it.
Deleting from the API
The CommentBox component needs a new method to delete individual comments.
class CommentBox extends React.Component {
// ...
_deleteComment(comment) {
jQuery.ajax({
method: "DELETE",
url: `/api/comments/${comment.id}`
})
}
}
Updating the Comment List
We will not wait for the API request to be finished before updating the component’s state. We will give our user immediate visual feedback, which is known as an optimistic update.
class CommentBox extends React.Component {
// ...
_deleteComment(comment) {
jQuery.ajax({
method: "DELETE",
url: `/api/comments/${comment.id}`
})
const comments = [...this.state.comments]
const commentIndex = comments.indexOf(comment)
comments.splice(commentIndex, 1)
this.setState({ comments })
}
}
We’re using the spread operator to clone the existing array to comments.
Taken from MDN web docs:
The const
declaration creates a read-only reference to a value. It does
not mean the value it holds is immutable, just that the variable identifier
cannot be reassigned. For instance, in the case where the content is an
object, this means the object’s contents (e.g., its parameters) can be altered.
splice() is used to add or remove items from an array. See Array.prototype.splice(). The first argument is the location to begin, the second is the number of items to delete.
Passing a Callback Prop to Comment
Events are fired from the Comment component. Since the event handler is defined on the parent component CommentBox, we’ll pass it as a prop named onDelete.
class CommentBox extends React.Component {
// ...
_getComments() {
return this.state.comments.map(comment => {
return (
<Comment
key={comment.id}
comment={comment}
onDelete={this._deleteComment.bind(this)}
/>
)
})
}
}
Adding an Event Listener to the Delete Button
Let’s add an event listener to the Delete Comment button and call the onDelete callback prop.
class Comment extends React.Component {
render() {
return (
// ...
<a href="#" onClick={this._handleDelete.bind(this)}>
Delete comment
</a>
// ...
)
}
_handleDelete(event) {
event.preventDefault()
this.props.onDelete(this.props.comment)
}
}
Inside of _handleDelete() we’re ensuring that the page isn’t reloaded when the link is clicked. Then we’re calling the function that was passed to the component as ‘onDelete’, and passing it the current comment.
Adding a Confirmation to the Delete Button
Let’s add an if statement and only call the onDelete callback prop if confirm was true.
class Comment extends React.Component {
render() {
return (
// ...
<a href="#" onClick={this._handleDelete.bind(this)}>
Delete comment
</a>
// ...
)
}
_handleDelete(event) {
event.preventDefault()
if (confirm("Are you sure?")) {
this.props.onDelete(this.props.comment)
}
}
}
confirm() is a native JavaScript function that displays a modal dialog with the message and two buttons (“OK” and “Cancel”).
Comments Aren’t Added to a Remote Server
We would like to post new comments to a remote server so they can persist across sessions.
class Comment extends React.Component {
// ...
_addComment(author, body) {
const comment = { id: this.state.comments.length + 1, author, body }
this.setState({ comments: this.state.comments.concat([comment]) })
}
}
The ID shouldn’t be generated on the client side, but should instead come from the server side. It’s also not making an Ajax request to sync the comments with the server side.
Posting Comments to a Remote Server
We learned how to add new comments using a form. Now let’s make sure the new comments are sent to a remote server so they can be persisted.
class Comment extends React.Component {
// ...
_addComment(author, body) {
const comment = { author, body }
jQuery.post("/api/comments", { comment }).success(newComment => {
this.setState({ comments: this.state.comments.concat([newComment]) })
})
}
}
Here we’re sending the arguments to the remote API, and then assigning the new comment which contains the server side generated ID into the comment array within the state object.
One-way Control Flow
Control flows from higher level components down to child components, forcing changes to happen reactively. This keeps apps modular and fast.
- The CommentBox component passes the _deleteComment() method to Comment as a callback
- The CommentBox component passes the _addComment() method to CommentForm as a callback
- The CommentBox component passes the author and body props to each Comment component
When a child component needs to send data back to the parent, it does so via a callback.
Total Recap
Here’s a review of the two most important things we learned in this section.
- Parent components can send data to child components using props.
- Child components can accept callback functions as props to communicate back with parent components.