During this workshop we will create a simple web application using a javascript library called React.
There will be two possible ways of completing the workshop.
Path A: Create a simple web application and deploy it online using GitHub Pages.
- Create a simple web application using React
- Use version control (Git) to manage your project
- Host the webpage using GitHub Pages
- Display information from an external Rest API
- Git installed on you machhine
- GitHub account
- Node 8.10 or later
- Run “node -v” in your terminal, if it does not return a version number node is not properly installed on your machine.
Path B: Create a simple web application using JSFiddle.
There are no requirement for this path.
- Create a simple web application using React
- Display information from an external Rest API
The jsfiddle is in 5 steps.
Follow the links below to complete them.
Open the terminal and navigate to where you want the project to be.
Run the following commands:
npx create-react-app workshop
cd workshop
npm start
Go to http://localhost:3000/ to see the sample application
Go to GitHub.
Click your avatar and select Your repositories in the dropdown.
Click New.
Write workshop
under Repository name
Click Create repository
Copy the command under …or push an existing repository from the command line
Open a new terminal window.
Navigate to the project we created.
Copy the command to the terminal.
Or type it, it was:
git remote add origin [email protected]:myusername/workshop.git
Change the myusername
part with your GitHub username.
Run:
git push -u origin master
Open the project in the editor.
Open package.json
and add the following to line.
"homepage": “https://myusername.github.io/workshop",
Change the myusername
part with your Gitub username.
Under scripts
add the following two lines.
"predeploy": "npm run build",
"deploy": "gh-pages -d build",
Go to the terminal.
Run:
npm install --save gh-pages
npm run deploy
Go to https://myusername.github.io/workshop/.
Change the myusername
part with your GitHub username.
Give it a minute, your sample application should appear there shortly.
Go to the editor.
Under src
create a file called MovieList.js
.
To begin with let's keep it very simple.
Functions is JavaScript are either done like this:
function add(a, b) {
return a + b;
}
Or they can be written like this:
const add = (a, b) => {
return a + b;
}
Or if we have a oneliner it can simply be written like this
const add = (a, b) => a + b;
These are called Arrow functions
and we will be using them here.
Write:
import React from 'react';
const MovieList = () => <div>MovieList</div>;
export default MovieList;
Now go to App.js
and change it so it looks like this:
import React from 'react';
import MovieList from './MovieList';
export default () => <div><MovieList /></div>
Note: We export
the MovieList
function so it can be imported in the App
.
If you go to http://localhost:3000 you should see that it now only displays MovieList
in the top left corner.
We will be using an open Studio Ghibli API to supply us with the information we want to display.
I found the API here listed with a bunch of other open API's. Open API's are great for practicing if you want to build your own project. Just be careful so you won't accidentally DDoS the kind souls hosting them.
Under src
create a new file called api.js
.
Here we will create two functions.
The first one is a utility function to GET from a Rest API that does not require an authentication.
The function return a promise which is an asynchronous operation.
const get = url => fetch(url).then(response => response.json());
The second one calls the get
function with the URL which points to our data.
export const getMovieList = () => get('https://ghibliapi.herokuapp.com/films');
api.js
should look like this now:
const get = url => fetch(url).then(response => response.json());
export const getMovieList = () => get('https://ghibliapi.herokuapp.com/films');
Note: We are only exporting the getMovieList
function.
We don't need to use the get
function in another file.
Now we need to do some changes to MovieList.js
.
First of all we need to change MovieList
from a function
to a class
and create an internal state
to manage the movie list.
import React, {Component} from 'react';
class MovieList extends Component {
constructor(props) {
super(props);
this.state = { movies: [] };
}
}
export default MovieList;
We also need to add a Lifecycle method that only calls our endpoint when our component is initiated.
The method we need is called componentDidMount.
Now lets import the getMovieList
from api.js
and call it when MovieList
is initiated.
After the data is fetched from the API we updated the movies
state with the information.
import React, { Component } from 'react';
import { getMovieList } from './api';
class MovieList extends Component {
constructor(props) {
super(props);
this.state = { movies: [] };
}
componentDidMount() {
getMovieList().then(movies => {
this.setState({ movies })
})
}
}
export default MovieList;
Now we need to create the DOM that is rendered in our browser along with our data.
We add a render
where we iterate through the list of movies and add them to the DOM.
import React, { Component } from 'react';
import { getMovieList } from './api';
class MovieList extends Component {
constructor(props) {
super(props);
this.state = { movies: [] };
}
componentDidMount() {
getMovieList().then(movies => {
this.setState({ movies })
})
}
render() {
return (
<div>
<h1>Ghibli movies</h1>
<div>
{this.state.movies.map((movie) => <div key={movie.id} >{movie.title}</div>)}
</div>
</div>
)
}
}
export default MovieList;
This looks pretty good, however I want to make one minor adjustment.
Destructing is a way of unpacking values from objects or arrays.
I want to use it to make the syntax a little cleaner.
It works like this:
const a = { b: { c : 'd' } };
const { b } = a; // { c : 'd' }
const { c } = b; // 'd'
After a few changes MovieList.js
should looks like this:
import React, { Component } from 'react';
import { getMovieList } from './api';
class MovieList extends Component {
constructor(props) {
super(props);
this.state = { movies: [] };
}
componentDidMount() {
getMovieList().then(movies => {
this.setState({ movies })
})
}
render() {
const { movies } = this.state;
return (
<div>
<h1>Ghibli movies</h1>
<div>
{movies.map(({ id, title }) => <div key={id} >{title}</div>)}
</div>
</div>
)
}
}
export default MovieList;
Go to http://localhost:3000 and you should now see a list of movie titles there.
Now lets display the rest of the data in a table.
Change MovieList.js
so it looks like this:
import React, { Component } from 'react';
import { getMovieList } from './api';
class MovieList extends Component {
constructor(props) {
super(props);
this.state = { movies: [] };
}
componentDidMount() {
getMovieList().then(movies => {
this.setState({ movies })
})
}
render() {
const { movies } = this.state;
return (
<div>
<h1>Ghibli movies</h1>
<table>
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Director</th>
<th>Producer</th>
<th>Release year</th>
<th>Score (Rotten Tomatoes)</th>
</tr>
</thead>
<tbody>
{movies.map(({ id, title, description, director, producer, release_date, rt_score }) => <tr key={id} >
<td>{title}</td>
<td>{description}</td>
<td>{director}</td>
<td>{producer}</td>
<td>{release_date}</td>
<td>{rt_score}</td>
</tr>)}
</tbody>
</table>
</div>
)
}
}
export default MovieList;
Go to http://localhost:3000 and view the changes.
Let's give this a minor facelift.
I'm using a color pallette from here.
Create a new file under src
called MovieList.css
.
Add the following css:
.wrapper {
background: #E68A3E;
display: flex;
flex-direction: column;
align-items: center;
}
table {
border-spacing: 0px;
width: 100%;
}
th {
text-align: start;
padding: 5px;
color: #443635;
}
td {
padding: 5px;
max-width: 700px;
vertical-align: top;
color: #443635;
}
tbody > tr:nth-child(odd) {
background: #F9F778;
}
tbody > tr:nth-child(even) {
background: #8EC0E8;
}
h1 {
color: #443635;
}
Go back to MovieList.js
and import MovieList.css
import React, { Component } from 'react';
import { getMovieList } from './api';
import './MovieList.css';
and add className="wrapper"
to the the first div
.
return (
<div className="wrapper">
MovieList.js
should now look like this.
import React, { Component } from 'react';
import { getMovieList } from './api';
import './MovieList.css';
class MovieList extends Component {
constructor(props) {
super(props);
this.state = { movies: [] };
}
componentDidMount() {
getMovieList().then(movies => {
this.setState({ movies })
})
}
render() {
const { movies } = this.state;
return (
<div className="wrapper">
<h1>Ghibli movies</h1>
<table>
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Director</th>
<th>Producer</th>
<th>Release year</th>
<th>Score (Rotten Tomatoes)</th>
</tr>
</thead>
<tbody>
{movies.map(({ id, title, description, director, producer, release_date, rt_score }) => <tr key={id} >
<td>{title}</td>
<td>{description}</td>
<td>{director}</td>
<td>{producer}</td>
<td>{release_date}</td>
<td>{rt_score}</td>
</tr>)}
</tbody>
</table>
</div>
)
}
}
export default MovieList;
Go to http://localhost:3000 and view the changes.
Lets make one minor adjustment. I want the numberic fields to centered so add className="number"
to the last two fields.
<td className="number">{release_date}</td>
<td className="number">{rt_score}</td>
Also add this after td in MovieList.css
.number {
text-align: center;
}
Let's finish this off by adding some basic filter functionality so we can easily find the movie we want.
We will only filter by the first four attributes.
We begin with adding a state
for our filters.
Change the constructor
so it looks like this.
class MovieList extends Component {
constructor(props) {
super(props);
this.state = {
movies: [],
filters: {
title: '',
description: '',
director: '',
producer: '',
}
};
}
Now lets add a generic filter component.
Under src
create a new file called Filter.js
import React from 'react';
const Filter = ({ filterKey, placeholder, value, onChange }) =>
<input
type="search"
placeholder={placeholder}
value={value}
onChange={event => {
onChange(filterKey, event)
}}
/>
export default Filter;
Go back to MovieList.js
and import Filter
component from Filter.js
.
import Filter from './Filter.js';
We should also create a generic filter function to the MovieList
component.
This should do the trick.
handleFilters = (key, event) => {
const value = event.target.value;
this.setState(({ filters }) => ({ filters: { ...filters, [key]: value } }))
}
Now edit the table header so it looks like this:
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Director</th>
<th>Producer</th>
<th>Release year</th>
<th>Score (Rotten Tomatoes)</th>
</tr>
<tr>
<th><Filter placeholder="Filter by title" filterKey="title" value={filters.title} onChange={this.handleFilters} /></th>
<th><Filter placeholder="Filter by description" filterKey="description" value={filters.description} onChange={this.handleFilters} /></th>
<th><Filter placeholder="Filter by director" filterKey="director" value={filters.director} onChange={this.handleFilters} /></th>
<th><Filter placeholder="Filter by producer" filterKey="producer" value={filters.producer} onChange={this.handleFilters} /></th>
<th /><th />
</tr>
</thead>
Now we have filtering but we need to apply it to our data.
We need to create another method to MovieList.js
to complete this.
filterMovies = () => {
const { movies, filters } = this.state;
const activeFilterKeys = Object.keys(filters).filter(key => filters[key] !== '');
if (activeFilterKeys.length === 0) {
return movies;
}
return movies.filter((movie) =>
activeFilterKeys.every(key => movie[key].toLowerCase().includes(filters[key].toLowerCase()))
)
}
Finally we need to filter the movie
state before we render the content of it.
Change the render
method so it looks like this:
render() {
const { movies, filters } = this.state;
const filteredMovies = this.filterMovies(movies);
return (
<div className="wrapper">
<h1>Ghibli movies</h1>
<table>
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Director</th>
<th>Producer</th>
<th>Release year</th>
<th>Score (Rotten Tomatoes)</th>
</tr>
<tr>
<th><Filter placeholder="Filter by title" filterKey="title" value={filters.title} onChange={this.handleFilters} /></th>
<th><Filter placeholder="Filter by description" filterKey="description" value={filters.description} onChange={this.handleFilters} /></th>
<th><Filter placeholder="Filter by director" filterKey="director" value={filters.director} onChange={this.handleFilters} /></th>
<th><Filter placeholder="Filter by producer" filterKey="producer" value={filters.producer} onChange={this.handleFilters} /></th>
<th /><th />
</tr>
</thead>
<tbody>
{filteredMovies.map(({ id, title, description, director, producer, release_date, rt_score }) => <tr key={id} >
<td>{title}</td>
<td>{description}</td>
<td>{director}</td>
<td>{producer}</td>
<td className="number">{release_date}</td>
<td className="number">{rt_score}</td>
</tr>)}
</tbody>
</table>
</div>
)
}
MovieList.js
should now look like this:
import React, { Component } from 'react';
import { getMovieList } from './api';
import './MovieList.css';
import Filter from './Filter';
class MovieList extends Component {
constructor(props) {
super(props);
this.state = {
movies: [],
filters: {
title: '',
description: '',
director: '',
producer: '',
}
};
}
componentDidMount() {
getMovieList().then(movies => {
this.setState({ movies })
})
}
handleFilters = (key, event) => {
const value = event.target.value;
this.setState(({ filters }) => ({ filters: { ...filters, [key]: value } }))
}
filterMovies = () => {
const { movies, filters } = this.state;
const activeFilterKeys = Object.keys(filters).filter(key => filters[key] !== '');
if (activeFilterKeys.length === 0) {
return movies;
}
return movies.filter((movie) =>
activeFilterKeys.every(key => movie[key].toLowerCase().includes(filters[key].toLowerCase()))
)
}
render() {
const { movies, filters } = this.state;
const filteredMovies = this.filterMovies(movies);
return (
<div className="wrapper">
<h1>Ghibli movies</h1>
<table>
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Director</th>
<th>Producer</th>
<th>Release year</th>
<th>Score (Rotten Tomatoes)</th>
</tr>
<tr>
<th><Filter placeholder="Filter by title" filterKey="title" value={filters.title} onChange={this.handleFilters} /></th>
<th><Filter placeholder="Filter by description" filterKey="description" value={filters.description} onChange={this.handleFilters} /></th>
<th><Filter placeholder="Filter by director" filterKey="director" value={filters.director} onChange={this.handleFilters} /></th>
<th><Filter placeholder="Filter by producer" filterKey="producer" value={filters.producer} onChange={this.handleFilters} /></th>
<th /><th />
</tr>
</thead>
<tbody>
{filteredMovies.map(({ id, title, description, director, producer, release_date, rt_score }) => <tr key={id} >
<td>{title}</td>
<td>{description}</td>
<td>{director}</td>
<td>{producer}</td>
<td className="number">{release_date}</td>
<td className="number">{rt_score}</td>
</tr>)}
</tbody>
</table>
</div>
)
}
}
export default MovieList;
Go to http://localhost:3000 and try out the filtering.
Lets add a final detail before we call it a day.
Under src
create a file called Filter.css
;
Add this to it:
.filter {
font-size: 16px;
width: 100%;
}
Import the css in Filter.js
and add className="filter"
to the input
.
Filter.js
should now look like this:
import React from 'react';
import './Filter.css';
const Filter = ({ filterKey, placeholder, value, onChange }) =>
<input
className="filter"
type="search"
placeholder={placeholder}
value={value}
onChange={event => {
onChange(filterKey, event)
}}
/>
export default Filter;
This is it.
Now lets end by pushing all out changes to the master and delpoying our project online.
In the terminal type:
git add --all
git commit -m "my ghibli movie list"
git push origin master
npm run deploy
Hope you enjoyed it and feel free to ask me anything after the workshop.