Learn the basics of React and see how its implementation compares to Angular.
Get started by cloning this repo and familiarizing yourself with the file structure.
react-friends/
-- dist/ /* this folder will contain all of the code we want available in the browser. */
---- index.html
-- src/ /* this folder will contain all of our pre-production code (code that hasn't yet been run through a build system). The majority of our work will happen here */
---- components/ /* React follows a component based architecture, this is the folder where we will be storing all of our components */
---- index.js /* This is the entry point of our application; the file to which everything will be connected */
---- styles.css
-- .babelrc /* We are using Babel alongside Webpack to compile our JSX/ES2015 into something browser-readable. This is where Babel is configured. */
-- webpack.config.js /* Webpack is the de facto build tool for React. A pre-built config has been provided but I highly recommend familiarizing yourself with it.
We'll start by using npm to install all of the dependencies necessary for our project. Don't forget to npm init
before trying to install!
Dev-Dependencies ( npm i --save-dev
)
webpack
- The module bundler we will be using as a build tool for this project.
- If you haven't already, you will also want to install this globally (
npm i webpack -g
)
webpack-dev-server
- A tool built for Webpack to allow live page reloads whenever a change is made.
- If you haven't already, you will also want to install this globally (
npm i webpack-dev-server -g
)
babel-core
- The core of Babel's parser
babel-preset-es2015
- A Babel preset allowing us to compile ES2015 to ES5
babel-preset-react
- A Babel preset allowing us to parse JSX
babel-loader
- Babel's Webpack plugin
style-loader
css-loader
- These allow Webpack to handle our CSS as well as our JS
Standard Dependencies ( npm i --save
)
react
- The core React library
react-dom
- The entry point of working with the DOM in React
Checkpoint: You should now be able to run webpack-dev-server -d
and navigate a browser window to http://localhost:8080.
Provided in components.png
is an image breaking down the individual components of our application.
- Red - The wrapper component of our entire application
- Green - This component contains the friends list and search fields
- Yellow - Each individual friend will also be a component
We'll start in index.js
, remember to check Webpack in your terminal for errors regularly!
- Start by importing React and ReactDOM, we'll need these to render our application into the DOM.
- Using ReactDOM's
render
method, render an<h1>Hello from index.js!</h1>
onto the div with the id ofreact-node
- Note that it is good practice to wrap this in a
document.addEventListener( "DOMContentLoaded", () => { //...
to ensure the application doesn't try to mount before the HTML document is ready.
- Note that it is good practice to wrap this in a
- Import
styles.css
( no need to save it to a variable ) so that Webpack will handle our styles as well.
Checkpoint: You should now be able to navigate to http://localhost:8080 and see a header with the text "Hello from index.js!". Your code should look something like this:
// index.js
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css"
document.addEventListener( "DOMContentLoaded", () => {
const reactNode = document.getElementById( "react-node" );
if ( reactNode ) {
ReactDOM.render(
<h1>Hello from index.js!</h1>
, reactNode )
}
} );
Now that we are set up and rendering, we can start building our first component. Create a new file inside of the components/
directory named App.js
and open it up. This will be the root component of our application.
- Start by importing React ( we won't need ReactDOM here! )
- Next create a new class named
App
that inherits fromReact.Component
. - Create a
render
method onApp
that returns the following JSX:
<div>
<h1>The <strong>facebook</strong> Friend Machine</h1>
<div className="friends">
</div>
</div>
- At the bottom of the page export
App
as the default export. - Import
App
toindex.js
and replace our current<h1>
with the App component.
Checkpoint: Your browser should now be displaying a styled header of "The facebook Friend Machine". The code should look something like this:
// index.js
// ...
if ( reactNode ) {
ReactDOM.render(
<App />
, reactNode )
}
} );
// App.js
import React from "react";
class App extends React.Component {
render() {
return (
<div>
<h1>The <strong>facebook</strong> Friend Machine</h1>
<div className="friends">
</div>
</div>
);
}
}
export default App;
With our header displaying and our app component ready for some child components, we can start building the next component: FriendsList.js
- Start by setting up
FriendsList.js
the same asApp
was; an exported class inheriting fromReact.Component
with arender
method that returns the following JSX:
<div>
<form className="form-inline searchForm" role="form">
<div className="form-group">
<input className="form-control" placeholder="Search For Friends" />
<select className="input-medium">
<option>Name</option>
<option>#Friends</option>
</select>
<select className="input-medium">
<option>Descending</option>
<option>Ascending</option>
</select>
</div>
</form>
<ul>
</ul>
</div>
- This component will need internal state, which means it needs a
constructor
. - The component's state should have three properties:
searchText
- Initially an empty stringorderBy
- Initially the string"name"
order
- Initially the string"ascending"
- Assign the above sections of the component's state to the appropriate input and select fields.
- Add appropriate values to each option.
Your FriendsList
class should now look something like this:
import React from "react";
class FriendsList extends React.Component {
constructor( props ) {
super( props );
this.state = {
searchText: ""
, orderBy: "name"
, order: "ascending"
};
}
render() {
return (
<div>
<form
className="form-inline searchForm"
role="form"
>
<div className="form-group">
<input
className="form-control"
placeholder="Search Anything For Friends"
value={ this.state.searchText }
/>
<select
className="input-medium"
value={ this.state.orderBy }
>
<option value="name">Name</option>
<option value="friend_count">#Friends</option>
</select>
<select
className="input-medium"
value={ this.state.order }
>
<option value="descending">Descending</option>
<option value="ascending">Ascending</option>
</select>
</div>
</form>
<ul>
</ul>
</div>
);
}
}
export default FriendsList;
To test this component we'll need to import it into App.js
and add the component inside of the div with the class of "friends". Because we haven't yet added an onChange
handler none of these values are editable, so let's fix that!
- Begin by creating a new method on the
FriendsList
class namedhandleChange
.- This method should take in two parameters,
field
andevent
. - When called, the method should use React's
setState
method to change the value of the correct property on state toevent.target.value
.
- This method should take in two parameters,
- Next we need to add
onChange
properties to our select and input elements, passing in ourhandleChange
method.- Don't forget to use
bind
to preserve the context ofthis
! - Make sure
onChange
'sfield
parameter matches the field on state that you want to change.
- Don't forget to use
Checkpoint: You should now be able to make changes to your search field and select boxes and have that value placed on FriendsList
's state
. Your code should look something like this:
import React from "react";
class FriendsList extends React.Component {
constructor( props ) {
super( props );
this.state = {
searchText: ""
, orderBy: "name"
, order: "ascending"
};
}
handleChange( field, event ) {
this.setState( { [ field ]: event.target.value } );
}
render() {
return (
<div>
<form
className="form-inline searchForm"
role="form"
>
<div className="form-group">
<input
className="form-control"
onChange={ this.handleChange.bind( this, "searchText" ) }
placeholder="Search For Friends"
value={ this.state.searchText }
/>
<select
className="input-medium"
onChange={ this.handleChange.bind( this, "orderBy" ) }
value={ this.state.orderBy }
>
<option value="name">Name</option>
<option value="friend_count">#Friends</option>
</select>
<select
className="input-medium"
onChange={ this.handleChange.bind( this, "order" ) }
value={ this.state.order }
>
<option value={ "descending" }>Descending</option>
<option value={ "ascending" }>Ascending</option>
</select>
</div>
</form>
<ul>
</ul>
</div>
);
}
}
export default FriendsList;
Create a new file named Friend.js
and follow the usual steps for creating a class with a render method that returns the following JSX:
<li className='friend'>
<img className="profile-pic" src='http://placebear.com/50/50.jpg' />
<h3>Cali Fornia</h3>
<div className="location">
Location: New Port Beach, California, United States
</div>
<div className="status">
Status: I hate the snow. I wish I was on the beach right now!!! <span className="hashtag">#ihateprovo</span>
</div>
<div className="num-friends">
Friends: 1,367
</div>
</li>
- Import
Friend
intoFriendsList
and place it inside theul
tag at the bottom.- You should now see a single friend listed, but we want to display our whole list!
- First we need our data, import
friends.js
intoFriendList.js
and save it to a variable namedfriends
. - At the top of the render method
map
over the array of friends to create an array ofFriend
components, passing infriend.name
,friend.pic_square
,friend.status
,friend.friend_count
, andfriend.current_location
as props.- Don't forget that every repeated item in React needs a unique
key
.friend.$$hashKey
would work well for this. - Be careful of null values!
- Don't forget that every repeated item in React needs a unique
- Adjust
Friend.js
to usethis.props
instead of the static data we included in our original JSX.
Checkpoint: You should now be displaying a large list of friends. Your code should look something like this:
// FriendsList.js
import React from "react";
import friends from "../../friends";
import Friend from "./Friend";
class FriendsList extends React.Component {
// ...
render() {
const friendsList = friends.map( friend => (
<Friend
currentLocation={ friend.current_location || {} }
friendCount={ friend.friend_count }
key={ friend.$$hashKey }
name={ friend.name }
pictureUrl={ friend.pic_square }
status={ friend.status ? friend.status.message : "" }
/>
) );
return (
// ...
<ul>
{ friendsList }
</ul>
);
}
// ...
// Friend.js
import React from "react";
class Friend extends React.Component {
render() {
return (
<li className='friend'>
<img className="profile-pic" src={ this.props.pictureUrl } />
<h3>{ this.props.name }</h3>
<div className="location">
Location: { this.props.currentLocation.city }, { this.props.currentLocation.state }, { this.props.currentLocation.country }
</div>
<div className="status">
{ this.props.status }
</div>
<div className="num-friends">
{ this.props.friendCount }
</div>
</li>
);
}
}
export default Friend;
As the final touch, we need to add sorting and filtering. For this we will use plain JavaScript.
- Using the values we have stored on our FriendList component's state and built in array methods sort, filter, and reverse the array of Friend components as expected.
- Warning: JavaScript's built in
.sort
does not reliably sort in Chrome. Either test in another browser or find a different sorting algorithm. - Your code should look something like this:
- Warning: JavaScript's built in
// FriendsList.js
// ...
const friendsList = friends
.filter( friend => friend.name.toLowerCase().indexOf( this.state.searchText.toLowerCase() ) !== -1 )
.sort( ( a, b ) => a[ this.state.orderBy ] > b[ this.state.orderBy ] )
.map( friend => (
<Friend
currentLocation={ friend.current_location || {} }
friendCount={ friend.friend_count }
key={ friend.$$hashKey }
name={ friend.name }
pictureUrl={ friend.pic_square }
status={ friend.status ? friend.status.message : "" }
/>
) );
const displayFriends = this.state.order === "ascending" ? friendsList : friendsList.slice().reverse();
// ...
- Currently we are only searching by name. Create a select that allows users to choose what to search by.
- Update the UI so that Friend components without location data do not display two empty commas.
If you see a problem or a typo, please fork, make the necessary changes, and create a pull request so we can review your changes and merge them into the master repo and branch.
© DevMountain LLC, 2016. Unauthorized use and/or duplication of this material without express and written permission from DevMountain, LLC is strictly prohibited. Excerpts and links may be used, provided that full and clear credit is given to DevMountain with appropriate and specific direction to the original content.