Nothing Special   »   [go: up one dir, main page]

ReactJs Project

Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 118

In this section we will build application with React. Let’s build it.

The name of our project is Vidly_apps


Install Vidly app

1- First install react: ---- npx create-react-app Vidly_apps


2- Navigate into your new folder after the setup has finished: --- cd Vidly_apps
3- Then we will install bootstrap and font-awesome
npm i bootstrap@4.1.0 npm i font-
awesome@4.7.0
4- Bot of them install now let’s add the CSS files to project, open your index.js
files and add them

Index.jsx

import "bootstrap/dist/css/bootstrap.css";
import "font-awesome/css/font-awesome.css";

building the movies component we want to show movies in table, our all
moviees are stored in JSON format, now we want to show our all movies:
data in table, to download all movies files link is here
https://github.com/SaidShahAhmadi/services our movies files are stored in
services folder. The services folder have two files
1. fakeGenreService.js
2. fakeMovieService.js these files include bunch of functions for
getting list of movies and genres, for adding new movies or deleting one as
will simple data, will building these application.

So copy this folder and past in src folder in your React application,

Now I want build movies component to display all list of movies in our
application.
On the movies page on the top we have total movies number that we have in
database. Below we have table with five column our column have (Title,
Genre,Stock,Rate), next to each movies we have Delete button, to delete movies
This is how we will build it  .

So, let’s build it .


I am going to start creating the movies component
1. in src folder we need to add components folder make it.
2. in components folder we add new file movies.jsx make it.

movies.jsx

import React, { Component } from 'react'

class Movies extends Component


{ state = { } render()
{ return ( );
}
}

export default Movies;

next, we need to go back to App.js, and add your movies component.

App.jsx
import React, { Component } from "react";
import Movies from "./components/movies"; 1
class App extends Component
{ render() { return (
<main
className="container">
<Movies /> 2
</main>
);
} } export
default App;

1. we import the movies component from component folder.


2. We make container class, and added the movies component <Movies/>

We make successful our movies component, now we want to render our table to display all the
movies.
Go to www.getbootstrap.com and make your table, because we use bootstrap table.
<table className="table table-hover">
<thead>
<tr>
<th>Title</th>
<th>Genre</th>
Table titles
<th>Stock</th>
<th>Rate</th>
</tr>
</thead>
<tbody>
<td>/td>
<td>/td>
</tbody>
</table>

Now, we want to get our movies and store them in array, and map each element of that array
to <tr> element.
1. We need to import getMovies function from fakeMoviesService.js, this is exported
function to return all movies, go and check it.
2. Go to movies.jsx component and import it.
import { getMovies } from "../services/fakeMovieService";

3. Here we have state object, we should add property movies: and initialize it by
getMovies() function
state = { movies: getMovies() };
finally, now we want to render our movies list in the table. To render our movies list we use
map() function to render the list.

we get each movies object and map to <tr> element, this is our output
To deleting record in react we use the filter() function.
First, we add the Delete button to each row in the table

Output

Deleing a Movies
Now we are adding Delete button to each row.
<td>
<button className="btn btn-danger btn-sm">Delete</button>
</td>

Second, add the delete event to delete the movie from list
<td>
=> this.handleDelete(movie)}
<button
className="btn btn-danger btn-sm"> Delete
</button>
</td>

Here we make the handleDelete function to delete the movies from list, this function
have handleDelete event, this function have movie object also to get all movies.

Now, let’s call this handleDelete method to delete the movies.


We should create new movies array that include all the movies except the, that we
have passed here, this is how we can an achieve this .
handleDelete = (movie) => {
const movies = this.state.movies.filter((m)
=> m._id !== movie._id); 1 this.setState({ movies });
2
};

1. We use the filter() method, to get all movies except the movie object, and inside
of filter() method, we pass arrow function here,
(const movies = this.state.movies.filter((m) => m._id !== movie._id);)
So, we get all the movies except the movie we passed here

2. Now we have new array of movies, we called (


this.setState({ movies }); ),and we passed the movies object.

This how we can Deleting in React.

Here is full Code


import React, { Component } from "react"; import
{ getMovies } from "../services/fakeMovieService";
class Movies extends Component
{ state = { movies:
getMovies() };
handleDelete = (movie) => { const movies =
this.state.movies.filter((m) => m._id !== movie._id);
this.setState({ movies }); };

render() {
return (
<React.Fragment>
<table className="table table-hover">
<thead>
<tr>
<th>Title</th>
<th>Genre</th>
<th>Stock</th>
<th>Rate</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{this.state.movies.map((movie) => (
<tr key={movie._id}>
<td>{movie.title}</td>
<td>{movie.genre.name}</td>
<td>{movie.numberInStock}</td>
<td>{movie.dailyRentalRate}</td>
<td>
<button
=> this.handleDelete(movie)}
className="btn btn-danger btn-sm"

</button>
</td>
</tr>

>
Delete

))}
</tbody>
</table>
</React.Fragment>
);
}
}
export default Movies;
Conditional Rendering
now it’s time to rendering message if there is any movies, it will count total movies
numbers, otherwise, it will show a message there is No movies.
const { length: count } = this.state.movies;

if (count == 0) return <p>There are No movies in the Database</p>;

return (
<React.Fragment>
<p style={{ fontWeight: "bold" }}>
Showing {count} movies in the Database
</p>

Add this code to inside of return method


const { length: count } = this.state.movies; if (count ==
0) return <p>There are No movies in the Database</p>;
return
(

<React.Fragment>
<p style={{ fontWeight: "bold" }}>
Showing {count} movies in the Database
</p>

<table className="table table-hover">


<thead>
<tr>
...
</tr>
</thead>
<tbody>
{this.state.movies.map((movie) => (
<tr key={movie._id}>
<td>{movie.title}</td>
<td>{movie.genre.name}</td> .
..

<td>
<button
=> this.handleDelete(movie)}
className="btn btn-danger btn-sm"
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
</React.Fragment>
Pagination

Pagination
The interface of this component is, what data does this component need, what are the
Event is going to raise, in terms of UI is going to bootstrap pagination component. Go
to bootstrap pagination section and add it to your apps.

Let’s build it 

Pagination – Component Interface

First of all, here in component folder.


1. first I want to add a new folder call it common, in common folder, we will
add all the reusable component, that are not specific to Doman our vidly
application such as pagination component, filter component, table
component, form component, etc.

2. so in common folder, add new file called it, pagination.jsx


3. now make your component, syntax, in this pagination file we use functional
component syntax.

Pagination.jxs

import React from "react";

const Pagination = (props) => {


return null;
};

export default Pagination;

whenever we want to build reusable component, for implementing that, you should
be thinking about interface of that component, what input that component is going
to receive, what event is going to writes. The best way to deiced the interface of
component is use it before implementing it.so let’s go to our movies.jsx file or
component.
In movies.jsx file
This is where we are going to use our new pagination component
1. In movies component first import pagination component
import Pagination from”. /common/pagination";

2. Down the bottom after the table tag, this is where we are going to use
Pagination component.
</table>
<Pagination />

3. Now let see what input we need to give this component , minimum we give this
component, total number of items as will the page size, so itemsCount,
pageSize and event.
- itemsCount => input of itemsCount is total number of
items.
- pageSize => input of pageSize is to calculate number of
pages and render them.
- Event => our component should have event, whenever
our user clicks on new page, and we set the method call it
handlePaeChange .
<Pagination
// without object destructuring
itemsCount={ this.state.movies.length} pageSize={4}
/>

Let’s clean our code.


1- itemsCount. In Preview section of Conditional Rendering we
already use object destructuring. So here we also use
object destructuring to clean our code. We set like this
(itemsCount={count})
2- pageSize. We can also use hard code value here, but as you
will find out shortly, we are going to use this value in
multiple places, so it’s better to store it in state.
state = { movies: getMovies(), pageSize: 4 };
We set this to (pageSize={this.state.PageSize})

<Pagination
// with object destructuring
1
itemsCount={count}
2 pageSize={this.state.pageSize}
> />

This our handlePageChange method


handlePageChange = (page) => {
console.log(page); };

Pagination – Displaying Pages


To render a pagination, we are going to use bootstrap pagination, here is markup that
we need to use in order pagination like this. We have ( nav,ul,li,pagination class,
pageitem, etc.)
so, let’s go add this to our
pagination component. Common/pagination.jsx
here we are returning our pagination classes.
Pagination.jsx

import React from "react";

const Pagination = (props) => {


return (
<nav>
<ul className="pagination">
<li className="page-item">
<a className="page-link">1</a>
</li>
</ul>
</nav>
);
};

export default Pagination;

save the changes, and back to browser, make sure our pagination render. You see
our pagination down.
We got one (1) page here, now we need to render this dynamically, based on
number of items and page size.

Dynamically rendering numbers


So here we need to have an array of page numbers like this [1, 2, 3] and then we
use map() method to map each page number in list item.

To ranging numbers form 1 up to pagesCount, how do we do this, there are


different way, but one way is use lodash, ladash is popular JavaScript library with
bunch of JavaScript functions.

So let’s install it. (npm i lodash@4.17.10) then import it to your pagination


component .
import _ from "lodash";

dynamically rendering numbers


import React from "react";
import _ from "lodash";

const Pagination = (props) => { const


{ itemsCount, pageSize } = props; 1

const pagesCount = Math.ceil(itemsCount /


pageSize); if (pagesCount === 1) return null;
const pages = _.range(1, pagesCount + 1);

2
return (
<nav> 3
<ul className="pagination">
{pages.map((page) => (
<li key={page} className="page-item">
<a className="page-link">{page}</a>
</li>
))}
</ul>
</nav>
);
};
export default Pagination;

1. first we do, in props we have some property, (itemsCount, pageSize) these


are part of interface of these component.
2. With these value we can calculate the number of pages like this. We
pagesCount which can be 2 or 3 whatever, and we divided itemsCount to
pageSize, based on that we should create an array. And we use Math.ceil()
method to convert the float number to integer number 9.1 to 9,
In second line If we have only single page we don’t want to render pagination
number.
In third line we use lodash to range from 1 to …. pagesCount, and here we
use lodash to generate an array with these numbers. We call _range()
method in this method our starting number is 1 and our end number is
pagesCount + 1, we have to add + 1 here because this method will not include
this end number itself, if pages count is four 4 it will return an array with 3
numbers 1,2,3 one two three. That why we need add +1 to make sure the last
page also included

3. Now we are going to map the item of the array list item. We get each page
and map to here, and Finley we need to render the page number dynamically
{page}

save the changes, and back to browser, make sure our pagination render
dynamically. You can see our pagination down.

So, we total have 9 movies and four 4 movies each page, we should get three 3
pages.

Pagination Handling Page Changes


Whenever user click on pagination, - we should change onPageChange event, and when
- we click on pagination, we want to highlight current page. Let’s do it.

1- So here in movies component currently we handling onPageChenge event of


pagination component, here in this event handler, we simply login the current page
on the console, but in pagination component we are erasing this component.
<Pagination
itemsCount={count}
pageSize={pageSize}
currentPage={currentPage}
/>

So back to pagination component, here we have an element tag,


<a className="page-link">{page}</a>

Whenever user click on this element tag, we should call onPageChange event, so
let call it
<a className="page-
link" => onPageChange(page)}>{page}</a>

We have arrow function, on the body we call onPageChange, and we pass


current page as argument.
Now check console, as we click different page, the current page is display on
the console. also we use object destructuring on top of code, add onPageChange
in object destructuring.

const { itemsCount, pageSize, currentPage, onPageChange } = props;

2- Highlighting current, as we click different pages we want to highlight current page.


We should apply active class conational to this list item li.
<li key={page}
className="page-item"
>
<a className="page-link" => onPageChange(page)}>
{page}
</a>
</li>

So, this page equal to current page we want add active class, that’s mean this
component we need to know current page, so we should extend the interface of this
component and also gave to current page, this make perfect since, because sometime user
may have selected given page.so we want to initialize this pagination component to
different page not page one 1 .
So here in props object destructuring, we should have currentPage .

const { itemsCount, pageSize, currentPage, onPageChange } = props;


now we need go back our movies component and pass currentPage as props
to pagination component .

currentPage={currentPage}

and here we use object destructuring to clean our code


const { pageSize, currentPage } = this.state;
on state of this component we keeping track parameter like pageSize and currentPage so
let’s go on the top initialize the currentPage, here in the state I am going to set
currentPage to 1
state = { movies: getMovies(), currentPage: 1, pageSize: 4 };

next we go to our event handler handlePageChange, we need to update the state


handlePageChange = (page) => {
this.setState({ currentPage: page });
};

Here we set currentPage property to this page

Note: whenever we click a new page onPageChange event is run, so we go back to


movies component, here we handle our event in handlePageChange method we update
the state, as we learn before whenever the state of component change that component all
it’ children are re-render, so the render method is call.
So go bottom of this component (movies) here we have pagination component the
pagination component re-render, this currentPage new props, it will receive a new page.

Last steps, is to apply the active class when the user click on the pagination page
<li key={page} className={page === currentPage ? "page-item
active" : "page-item"}>
<a className="page-link" => onPageChange(page)}>
{page}
</a>
</li>

Here we say, if this page we are rendering equal to currentPage, we are return
pageitem pulse active class, otherwise we are only return page-item.

See full code here


Movies.jsx
import React, { Component } from "react"; import
Pagination from "./common/pagination"; import { getMovies
} from "../services/fakeMovieService"; import { times }
from "lodash";

class Movies extends Component { state = { movies:


getMovies(), currentPage: 1, pageSize: 4 };

handleDelete = (movie) => { const movies =


this.state.movies.filter((m) => m._id !== movie._id);
this.setState({ movies });
};
handlePageChange = (page) => {
this.setState({ currentPage: page });
};
render() {
//object destructuring
const { length: count } = this.state.movies;
const { pageSize, currentPage } = this.state;

if (count === 0) return <p>There are No movies in the Database</p>;

return (
<React.Fragment>
<p style={{ fontWeight: "bold" }}>
Showing {count} movies in the Database
</p>
<table className="table table-hover">
<thead>
<tr>
<th>Title</th>
<th>Genre</th>
<th>Stock</th>
<th>Rate</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{this.state.movies.map((movie) => (
<tr key={movie._id}>
<td>{movie.title}</td>
<td>{movie.genre.name}</td>
<td>{movie.numberInStock}</td>
<td>{movie.dailyRentalRate}</td>

<td>
<button
=> this.handleDelete(movie)}
className="btn btn-danger btn-sm"
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table> <Pagination
itemsCount={count}
pageSize={pageSize}
currentPage={currentPage}
> />
</React.Fragment>
);
}
}
export default Movies;

pagination.jsx
import React from "react";
import _ from "lodash";

const Pagination = (props) => { const { itemsCount, pageSize,


currentPage, onPageChange } = props;

const pagesCount = Math.ceil(itemsCount /


pageSize); if (pagesCount === 1) return null;
const pages = _.range(1, pagesCount + 1);
return
(
<nav>
<ul className="pagination">
{pages.map((page) => (
<li key={page} className={page ===
currentPage ? "page-item active" : "pageitem"}
>
<a className="page-link" => onPageChange(page)}>
{page}
</a>
</li>
))}
</ul>
</nav>
);
};
export default Pagination;
Pagination – Paginating Data

So, now we have build different pieces, we are ready to paginate the data, here in
(movies) component we are rendering, the currently we are rendering the movies that we
have in state object, this is the original list of movies, we don’t want to change this list, as
we paginate the movies or search for them or filter them, we want to create a sprite array
that is the array that we are going to render here.
Here is one solution here in render method we have pageSize and currentPage
const {pageSize, currentPage} = this.state;

base on this value we can paginate the data, so here if count is not zero 0 after this
if (count === 0) return <p>There are No movies in the Database</p>;

we are create a new array, we call that movies and paginate the data, and create a new array.

The pagination algorithms is something that we probably need to use in different places
of this application, or even in different application.so we don’t want to those algorithms
here, in (movies component), firstly it will load this method, second we won’t be able to
reuse it.

Here in project folder under the src folder add new folder call it utils , here we will have all
utilities classes and functions, so let’s add a new file here call it paginate.js

Open paginate.js.
1- In this module import lodash because lodash has bunch of utilities method we use
implement pagination .
import _ from "lodash";

2- Export a function the parameter of this method are (items,pageNumber, pageSize) .


export function paginte(items,pageNumber,pageSize){

3- To paginate the data first, we need to calculate the starting index of (items) on this
(pageNumber) page. So here is formula. For that
export function paginte(items, pageNumber, pageSize) {
const startIndex = (pageNumber - 1) * pageSize;
}

4- now we can use lodash to go to this startIndex and take all the items for current page.
return_(items).slice(startIndex).take(pageSize).value();

- _(items) this will return lodash object and then we can change all
the lodash method.
- .slice() this method will slice are array starting from (startIndex).
And create a new array
- .take() so we gave an array and total number of array we want to
take from the array.
See the full code here

Paginate.js

import _ from "lodash";


1
2
export function paginte(items, pageNumber,
pageSize) { const startIndex = (pageNumber -
1) * pageSize; 3

return
_(items).slice(startIndex).take(pa geSize).value(); }
4

So this how we can paginate data of client side 


Next let’ go back to movies component and import paginate algorithms module.
1. Import
import { paginate } from "../utils/paginate";
2. And go to render method after if conditions . if (count === 0) return

<p>There are No movies in the Database</p>; and paginate our movies


const movies = paginte(allMovies, currentPage, pageSize);

- We store our function in movies var


- Paginate() this is the function we have declare in paginate.js,
and we gave array of movies,(allMovies) here (allMovies) is
in object destructed
const { pageSize, currentPage, movies: allMovies } = thi
s.state;

and also we rename it to allMovies .


- Paginate() function has also (currentPage and pageSize)
parameter.
3. Finally go to .map() method and change (this.state.movies) expression to
moves
{movies.map((movie) => (
So now every page has 4 movies, we can go to different pages and see different movies. We
have successfully implemented pagination

See full code.


Movies.jsx

import React, { Component } from "react"; import


Pagination from "./common/pagination"; import { times }
from "lodash"; import { getMovies } from
"../services/fakeMovieService"; import { paginate,
paginte } from "../utils/paginate";

class Movies extends Component { state = { movies:


getMovies(), currentPage: 1, pageSize: 4 };

handleDelete = (movie) => { const movies =


this.state.movies.filter((m) => m._id !== movie._id);
Output
this.setState({ movies });
};
handlePageChange = (page) => {
this.setState({ currentPage: page });
};
render() {
//object destructuring
const { length: count } = this.state.movies; const
{ pageSize, currentPage, movies: allMovies } = this.state;

if (count === 0) return <p>There are No movies in the Database</p>;

const movies = paginte(allMovies, currentPage, pageSize);


return (
<React.Fragment>
<p style={{ fontWeight: "bold" }}>
Showing {count} movies in the Database
</p>
<table className="table table-hover">
<thead>
<tr>
<th>Title</th>
<th>Genre</th>
<th>Stock</th>
<th>Rate</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{movies.map((movie) => (

<tr key={movie._id}>
<td>{movie.title}</td>
<td>{movie.genre.name}</td>
<td>{movie.numberInStock}</td>
<td>{movie.dailyRentalRate}</td>

<td>
<button
=> this.handleDelete(movie)}
className="btn btn-danger btn-sm"
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table> <Pagination
itemsCount={count}
pageSize={pageSize}
currentPage={currentPage}
> />
</React.Fragment>
);
}
}
export default Movies;

and this pagination.jsx


import React from "react";
import _ from "lodash";

const Pagination = (props) => { const { itemsCount, pageSize,


currentPage, onPageChange } = props;

const pagesCount = Math.ceil(itemsCount /


pageSize); if (pagesCount === 1) return null;
const pages = _.range(1, pagesCount + 1);
return
(
<nav>
<ul className="pagination">
{pages.map((page) => (
<li
key={page}

className={page === currentPage ? "page-item active" :


"pageitem"}
>
<a className="page-link" => onPageChange(page)}>
{page}
</a>
</li>
))}
</ul>
</nav>
);
};
export default Pagination;

finally
paginate.js algorithms in utils folder

import _ from "lodash";

export function paginte(items, pageNumber, pageSize) { const startIndex =


(pageNumber - 1) * pageSize; return
_(items).slice(startIndex).take(pageSize).value(); }

Pagination – Type checking


<Pagination
itemsCount={count}
pageSize={pageSize}
currentPage={currentPage}
/>
So, we have build this pagination component, here we one issue, what if we forget the
props and pass wrong type of value. For example.

If to itemsCount we pass abc string.


<Pagination
itemsCount="abc"{count}
pageSize={pageSize}
currentPage={currentPage}

/>
See your application, we get only one pagination number.
This is how we create bugs in our application

1. The solution of this bugs is, to use the Type Checking. So install the types checking
( npm i prop-types@15.6.2 )

2. now back to pagination component and import the propTypes


import PropTypes from "prop-types";

3. after we defend the pagination component, the property we defend, with this
property, the type checking requirement for this component.
Pagination.PropTypes = {
itemsCount: PropTypes.number.isRequired, pageSize:
PropTypes.number.isRequired, currentPage:
PropTypes.number.isRequired, onPageChange:
PropTypes.func.isRequired, };

End of this exercise


We have build pagination component 
In this new section let’s improve our application.
I want to implement to filtering, sorting the movies, depending the item we
select, we will see different result here. The layout of this component, we use the
bootstrap list-group.
The Genres that we have here it’ come from our fakeGenreService in services
folder we fakeGenreService.js file, this fakeGenreService we added at begging
of course.
In this module we have single function getGenres() that return an array of three
Genre

This is how we will build it 

Sorting

filtering

Filtering – Component interface


1. Let’s start by adding new file in common folder and called it listGroup.jsx. 2.
Here in top we import React. (imr) is the shortcut
import React from "react";

3. And create functional component and called it ListGroup.


import React from "react";

const ListGroup = () => { return null;


}; export default ListGroup;

as we told before, whenever create component, we should be thinking about


the interface of that component, and the best way is to use that component
before actually implementing.
4. Let’ go to movies component (movies.jsx), here in top import listGroup
component.
import ListGroup from "./common/listGroup";

5. Now let’s go to the render() method, scroll down , we have


<React.Fragment> inside we have paragraph ,<p> table , <table> and
pagination <pagination>, we gone add bootstrap row <row> with tow
column col, In the right column we have our new listGroup and in the left
column we have table.
<div className="row">
<div className="col-3"></div>
<div className="col"></div>
</div>

- In the first column we have our list group col-3


- In the second column we have our table col
So let’s add it
<div className="row">

<div className="col-3">
//listgroup component 1
<ListGroup />
</div>

<div className="col"> 2
<p style={{ fontWeight: "bold" }}>
Showing {count} movies in the Database
</p>
<table className="table table-hover">
<thead>
<tr>
<th>Title</th>
<th>Genre</th>

</div> //end of col
</div> //end of row

6. In first column we have our ListGroup component, this ListGroup component


should get list of items to render, so let’s add items property, we set this to
(items={this.state.genres})
<div className="col-3">
<ListGroup items={this.state.genres} /> </div>

- Currently we don’t add the genres to our state, so let’s go


and add it. Same as we added the movies property, we
can also add genres also same. Genres: getGenres(); this
perfect defined for simple application, but in real world
application, you going to call back-end services to get
these movies and genres. So the right place initializes this
property is use componentDidMount() lifecycle hooks, so
now we initialize genres and movies to empty array.

state = {
movies: [],
Empty array
genres: [],
currentPage: 1,
pageSize: 4
};

- Let’s import genres from services folder


import {getGenres} from "../services/fakeGenreService";

- And also create componentDidMount() lifecycle hooks.


And add genres and movies. Here in
componentDidMount we are going to call setState()
method and set movies:getMovies() and genres:getGenres().
componentDidMount(){
this.setState({movies:getMovies(), genres:getGenres()}
) }

The reason we initialize these properties to an empty array is


because there is going to take some time until we get the
data from the server, during this time, make sure movies and
genres are not undefined, otherwise we will get run time
error.

- So we have set the genres property, now back to render()


method, here is List Group we set items property. Now
Thinking anther input for this component.

- So let’s talk about Events, what Event do we need here, i


believe whenever the user select the item, we should
notify, so we can filter the list of movies, add this an
event ( . when item
select, we handle genre

<div className="col-3">
<ListGroup
items={this.state.genres}
> />
</div>

- Let’s go and make this method handleGenreSelect() in this


method we should get the genre that is currently selected.

handleGenreSelect = (genre) =>


{ console.log(genre);
};

With all these we have define, the interface for this new
component.

Filtering – Displaying items


To render a listGroup first, let’s see what markup we need to work within
bootstrap website under documentation find out list group. Here is markup we need
to work with it ul class list-group inside this ul we should have li class list-
groupitem.

Now open your listGroup component listGroup.jsx file. Here in list group component
let’s return ul and li .
Shortcut is: type ( ul.list-gourp>li.list-group-item) and press tab button in
keyboard.

listGroup.jsx
<li className="list-group-item"></li>
</ul>
);
}; export default
ListGroup;

Now we want to render the items dynamically


1. First we need to pass props here to our function as argument.

import React from "react";

const ListGroup = () => {


return (
<ul className="list-gourp">
2. Use object destructuring to read items property from the props
object.
const {items} = props;

3. and then use map() method each item in this array to list item element.
<ul className="list-group">
{items.map((item) => (
<li key={item._id} className="list-group-item">
{item.name}
</li>
))}
</ul>

Here is full code


import React from "react";

const ListGroup = (props) =>


{ //object destructuring
const { items } = props;

return (
<ul className="list-group">
{items.map((item) => (
<li key={item._id} className="list-group-item">
{item.name}
</li>
))}
</ul>
);
};
export default ListGroup;

now let’s test the application


On the left side we have list of Genre, we successfully rendering the
items.

4. but there are some issues with the implementation.


Here in listGroup.jsx component, we are using that each item has these
two property (item._id and item.name), what if we are working
with different kind of object, which doesn’t have these two property,
maybe inside of (item._id) that property is called Value, we want to
make our listGroup more fixable.
To solve this issue, we need to pass two more props here, these props
determine the name of target property (id and name)

Go to the movies.jsx component, here is ListGroup component and


add these two props (textProperty and valueProperty)
<ListGroup

items={this.state.genres}
> textProperty="name"
valueProperty="_id" />

So, when we using listGroup we passing list of items


(items={this.state.genres}), we should also pass the name of text
(textProperty="name") and value property (valueProperty="_id"), so
we can work any kind of object.

5. Now back in listGroup.jsx component, here in top we need add these two new
props.
//object destructuring
const { items, textProperty, valueProperty } = props;

6. Next, here in <li> markup in the key={item._id} change it to


key={item[valueProperty]} and also change the {item.name} to
{item[textProperty]}, we use the [ ] to access the property dynamic.
<ul className="list-group">

{items.map((item) => (
<li key={item[valueProperty]} className="list-group-
item">
{item[textProperty]}
</li>
))}
</ul>
Now we can use this listGroup with any kind of list

Filtering – Default Props


Let’s clean the code, we have added these two property (props) to our ListGroup to make
it more flexible, however these two props make the interface of this component more
complex. Thinking about TV-Remote control, if that Remote have 50 buttons, it become
very hard to use it, if it has les button, it much easy to use that.

We have same principle here, how we can solve this problem, we set default value for
this props, and then in the future we are working with different kind of object that doesn’t
have these two property we have overwrite the default values.

1. So, back to listGroup.jsx component, at end of this component we are


set default props.

ListGroup.defaultProps = { textProperty:
"name", valueProperty: "_id",
};

We type ListGroup and defaultProps, we added new property called


defaultProps and set it to an object.

Make sure spelling properly, and use right notation and use camel-
notation. So in this object we add our props and their default
value.

2. With this we no longer use textPeoperty and valueProperty in


movies.jsx component, so go to the movies component and delete
from listGroup component these two props.

Here is full Code.  listGroup.jsx

import React from "react";

const ListGroup = (props) => {


//object destructuring
const { items, textProperty, valueProperty } = props;

return (
<ul className="list-group">
{items.map((item) => (
<li key={item[valueProperty]} className="list-group-item">
{item[textProperty]}
</li>
))}
</ul>
);
};

Default Props
ListGroup.defaultProps = {
textProperty: "name",
valueProperty: "_id",
};

export default ListGroup;

Filtering – Handling selection


Now let’s work on items selection, when we selecting our genres, in listGroup.jsx
component, here we want to handle the onClick event of the list item, the custom event
called => onItemSelect(item)}, we use arrow function and here we called
onItemSelect prop, because in movies.jsx component we use ListGroup component
and we passed onItemSelect prop.

1. Back to listGroup component on the top, we should extract this property


from our props object, in the destructing.

const { items, textProperty, onItemSelect


valueProperty,
} = props;

2. Our onClick event with arrow function.to this function we pass the
current item that we are rendering as argument.

return (
<ul className="list-group">
{items.map((item) => (
<li
=> onItemSelect(item)}
key={it em[valueProp
erty]} className="list-group-item"
>
{item[textProperty]}
</li>
))}
</ul>
);
Now test the application, open your console and click the genre .

3. One things are massing, is the active class, so whenever we click on


genre that genre should be active. In listGroup component, when we
rendering list item li, we should dynamically apply the active class if this
item equal to selecting item. so that means this list group should now
the selected item.
So, back to the movies.jsx component and let’s go to
handleGenreSelect method,
handleGenreSelect = (genre) =>
{ console.log(genre);
};

Remove console.log(genre), here we login the genre in to console, but we


should store it in the state. So we set the state to
(this.setState({selectedGenre :genre})) ,

handleGenreSelect = (genre) => {


this.setState({ selectedGenre: genre }); };

we say selected to this genre,so as we know, whenever we update the


state this component and all its children are re-render.so our listGroup
will be re-render, and here we need to pass new prop
(selectedItem={this.state.selectedGenre}), to listGroup component.

<ListGroup
items={this.state.genres}
selectedItem={this.state.selectedGenre}
> />

Now our list group know, what is the selected item, so it can apply the
active class to that item .

4. Back to our listGroup component, on the top in destructing section we need


to extract, this new prop (selectedItem)

const ,selectedItem,
{ items,textProperty,valueProperty
onItemSelect } = props;

now, when rendering the list item (li) we should to set the class
dynamically, if this item we are rendering equal to the
selectedItem we should return (list-group-item active) class,
otherwise we return (list-group-item)

return(
<ul className="list-group">
{items.map((item) => (
<li
=> onItemSelect
(item)}
key={item[valueProperty
]}
className={
item === selectedItem?

}
Dynamic class
>
{item[textProperty
]}
</li>
))}
</ul>
);
};
"list-group-item active" : "list-group-item"

Save the changes and check your application

Now we are handling the selection of an item, we are ready to filter the movies based on the
selected genre.

Here is full Code  movies.jsx

import React, { Component } from "react";

(...)
class Movies extends Component
{ state = { movies: [],
genres: [], currentPage: 1,
pageSize: 4,
}; componentDidMount() { this.setState({ movies: getMovies(),
genres: getGenres() }); } handleDelete = (movie) => { const
movies = this.state.movies.filter((m) => m._id !== movie._id);
this.setState({ movies });
}; handlePageChange =
(page) => {
this.setState({ currentPage: page });
}; handleGenreSelect =
(genre) => {
this.setState({ selectedGenre: genre });
};
render() {
//object destructuring const { length:
count } = this.state.movies;
const { pageSize, currentPage, movies: allMovies } = this.state;
if (count === 0) return <p>There are No movies in the
Database</p>; const movies = paginte(allMovies, currentPage,
pageSize);

return (
<React.Fragment>
<div className="row">
<div className="col-3">
<ListGroup
items={this.state.genres}
selectedItem={this.state.selectedGenre}
> />
</div>

( ... )

ListGroup.jsx

import React from "react";


const ListGroup = (props) => { //object destructuring const { items,
textProperty, valueProperty, selectedItem, onItemSelect } = props;

return (
<ul className="list-group">
{items.map((item) => (
<li => onItemSelect(item)}
key={item[valueProperty]} className={ item ===
selectedItem ? "list-group-item active" : "list-group-item"
}
>
{item[textProperty]}
</li>
))}
</ul>
);
};

//default props
ListGroup.defaultProps =
{ textProperty: "name",
valueProperty: "_id",
}; export default
ListGroup;

Filtering – Implementing Filtering


Here in movies.jsx component the render method on after (if statement ),
if (count === 0) return <p>There are No movies in the Database</p>;

we calling the paginate method to paginate all the moves, we need to apply filter before
pagination, because the number of pages should be based on the number of filter movies so here
first we need to get the selected genre.
const movies = paginte(allMovies, currentPage, pageSize);

1- In object destructing let’ add (selectedGenre).


const { pageSize, currentPage,selectedGenre, …}=this.state;

2- Now before pagination we need to do filter, so we define new const (filtered), here
we say (if selected genre is true, we going to apply a filter so we get all movies and filter
them).
const filtered = selectedGenre
? allMovies.filter((m) => m.genre._id === selectedGenre._id)
: allMovies;

So, this how we do filtering if (selectedGenre) is true we get all (allMovies) and
filter them, such that the genre of each movie (m => m.genre._id) equal to the
selectedGenre, otherwise if there is no selected genre, we show list of all movies
(allMovies)

3- Next instead of passing (allMovies) in paginate function,

const movies = paginte(allMovies, currentPage, pageSize);

we need to pass filtered movies.


const movies = paginte(filtered, currentPage, pageSize);

4- And then go to down, where we use pagination component


<Pagination

itemsCount={count}
pageSize={pageSize}
currentPage={currentPage}
> />

Here in itemsCount={count}, Instead of passing this count const, we need to


change this expression and pass (filtered.length)
<Pagination
itemsCount={filtered.length}
pageSize={pageSize}
currentPage={currentPage}
> />

So, the number of pages’ render properly

5- Now we change the number of total movies,


<p style={{ fontWeight: "bold" }}> Showing {count} movies in the Database
</p>

we need to change this expression and pass to (filtered.length)


<p style={{ fontWeight: "bold" }}>
Showing {filtered.length} movies in the Database
</p>
Now save the changes and check the application  , when we selecting the genre it filtering the movies .
For example, let’s select comedy genre , you only see comedy movies in table.

Our filtering is working, but we need to add another item to this list called it (All Genre) , to do
that next  .

Filtering – Adding “All Genres”


Now we need to add new item add top of this list, we call that All Genre, so back to movies.jsx
component.
1- Here in movies component, componentDidMount() method, this is where we
use genres property of the state object

componentDidMount() {
this.setState({ movies: getMovies(), genres: getGenres() }
);
}
2- We need a small change here, so we defined new const call it (genres) and we
set to a new array, we spread the array that is return from (getGenres() )
function, (const = genres =[ …getGenres()]) so now we have all existing genres
and then we can put a new object at the beginning of this array ({}) here we
set the name property to “All Genres” ( {name: ‘All Genres’}, …getGenres() )
and we do not to set id because valid genre in the database, this is just an item
at top of the list. And also pass id to our new genre (All Genres) this genre
don’s have id, if we not pass the id, so it’s gave error so pass the id (_id:’’), to
an empty string.

componentDidMount() {

const genres = [{_id:’’,name: "All Genres" }, ...getGenres()]


;

this.setState({ movies: getMovies(), genres: getGenres() });


}

Now we have an array of genre, next we pass the array in setState() method to
“genres” , so we change the genres:getGenres() to new array
(genres:genres)
componentDidMount() {
const genres = [{ name: "All Genres" }, ...getGenres()];

this.setState({ movies: getMovies(), genres: genres });


}

With this we get All Genres on the top

We have successfully added at the top of genre the “All Genres”, but here one
problem, when we click to (All Genres) our movies are not showing.

Here are the reasons


In our render method, where we filter list of movies,
render() {

const filtered = selectedGenre


? allMovies.filter((m) => m.genre._id === selectedGenre._id)
: allMovies;

We are checking to see if selected genre (selectedGenre) is true, if it is true we are


filtering the movies, and we get each movie, and we make sure that the id of
(m.genre_id) genre equals the id of selected genre (selectedGenre_id).
In this case our selected genre doesn’t have ID because we only set the name
property, right so we don’t see any movies when we select (All Genres) \

To fix this problem we need to change this condition ( const filtered =


selectedGenre ) something like this (const filtered = selectedGenre &&
selectedGenre._id )

const filtered = selectedGenre && selectedGenre._id


? allMovies.filter((m) => m.genre._id === selectedGenre._id)
: allMovies;
Here we say if selectedGenre and the ID of selected genre (selectedGenre._id)
are both true then we apply the filter otherwise we get all the movies.

Let’s test our application 

page and
then go back to the Action movies, we don’t see anything
here. Even at top showing 3 movies in the Database.
Here is one problem anther, if I go to the second

Here is the reason, to fix this issue whenever we change the filter we
should reset the page to one 1. So back In to movies.jsx component here in
handleGenreSelect() method.

handleGenreSelect = (genre) => {


this.setState({ selectedGenre: genre });
};

We should also reset currentPage to one 1 (currentPage:1) this will fix this
bug.
handleGenreSelect = (genre) => {
this.setState({ selectedGenre: genre, currentPage: 1
}); };
Let’s test our application . It’ working 

Filtering – Extracting MoviesTable


So we have implement the pagination and filtering , but there is tiny issue in the output of
movies component, see what’s going on here in movies.jsx component.
Message

Table

Left column

Pagination

1- we have two (div) which we use to implement a grade layout


2- here in left column we have (listGroup) that is custom component that we have built
3- in the right column we have message, below that we have (table) with all these details
4- then after that we have the (Pagination) component

here is our movies.jsx component


movies.jsx
import React, { Component } from "react"; import
Pagination from "./common/pagination"; import ListGroup
from "./common/listGroup"; import _ from "lodash";
import { getMovies } from
"../services/fakeMovieService"; import { getGenres }
from "../services/fakeGenreService"; import { paginte }
from "../utils/paginate";

class Movies extends Component


{ state = { movies: [],
genres: [], currentPage: 1,
pageSize: 4,
// defaultSelect: "list-group-item active",
}; componentDidMount() { const genres = [{ name:
"All Genres" }, ...getGenres()];
this.setState({ movies: getMovies(), genres: genres }); }
handleDelete = (movie) => {
const movies = this.state.movies.filter((m) => m._id !== movie._id);
this.setState({ movies });

}; handlePageChange = (page)
=> {
this.setState({ currentPage: page });
}; handleGenreSelect = (genre) => {
this.setState({ selectedGenre: genre, currentPage: 1 }); };
render() {
//object destructuring const { length:
count } = this.state.movies; const
{ pageSize, currentPage,
selectedGenre, movies: allMovies,
} = this.state;
if (count === 0) return <p>There are No movies in the Database</p>;
const filtered = selectedGenre
&& selectedGenre._id
? allMovies.filter((m) => m.genre._id === selectedGenre._id)
: allMovies;
const movies = paginte(filtered, currentPage, pageSize);
return (
<React.Fragment>
<div className="row"> 1 div layout
<div className="col-3">
<ListGroup
items={this.state.genres}
selectedItem={this.state.selectedGenre} 2 ListGroup
/>
</div>

<div className="col">
<p style={{ fontWeight: "bold" }}>
Showing {filtered.length} movies in the Database
</p>
<table className="table table-hover">
<thead>
3 Table
<tr>
<th>Title</th>
<th>Genre</th>
<th>Stock</th>
<th>Rate</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{movies.map((movie) => (
<tr key={movie._id}>
<td>{movie.title}</td>
<td>{movie.genre.name}</td>
<td>{movie.numberInStock}</td>
<td>{movie.dailyRentalRate}</td>

<td> <button
=> this.handleDelete(movie)}
className="btn btn-danger btn-sm" >
Delete
</button>
</td>
</tr>
))}
</tbody>
</table> <Pagination
itemsCount={filtered.length} pageSize={pageSize}
currentPage={currentPage}
> 4 Pagination
/>
</div>
</div>
</React.Fragment>
);
}
} export default
Movies;

issue we have here is make level abstraction, so here we have component that are high
level simplify abstract pagination and ListGroup, what we have elements this table that is
low level is too details, so we don’t have consistency in our code.

Let’s clean our Code  .

1. first thing I am doing now extract a new component, call it moviesTable.jsx and
move all table details about render table and insert it to moviesTable component.
So, here in component folder add a file by name of moviesTable.jsx, Note
that this is not under the common folder , because this is not re-usable
component, it is the table of movies that we have on the movies page.
- Open moviesTable.jsx component and import react
import React from 'react';

- and create a functional component (sfc) and pass props object.


import React from 'react';

const moviesTable = (props) =>


{ return ( );
}

export default moviesTable;

- and back to movies.jsx component and select all the table markup cut it and
pass it in to moviesTable.js.

moviesTable.jsx

import React from "react";

const moviesTable = (props) => { return (


<table className="table table-hover">
<thead>
<tr>
<th>Title</th>
<th>Genre</th>
<th>Stock</th>
<th>Rate</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{movies.map((movie) => (
<tr key={movie._id}>
<td>{movie.title}</td>
<td>{movie.genre.name}</td>
<td>{movie.numberInStock}</td>
<td>{movie.dailyRentalRate}</td>
<td> <button
=> this.handleDelete(movie)}
className="btn btn-danger btn-sm"
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
);
}; export default
moviesTable;

good job 
- now make a few changes here in moviesTable.jsx, so first we should get movies
from props, make object destructing at the beginning of this component, with this
we can get an idea what is the interface of this component.
const { movies,onDelete } = props;

- now what else we need to change here, down we have one handler method
(handleDelete) button,
<button =>
this.handleDelete(movie)}
className="btn btn-danger btn-sm"
> Delete
</button>

Since we are passing movies for props, we are not suppose to modify the props, so
the actual state is store in the movies.jsx component, here we should make our
event and Let them movies.jsx component delete or like given movie,
So here in moviesTable.jsx component, on the top in the object destructing
section, we should pick some more property from our props object, so add it
(onDelete) this is functional references Change (this.handleDelete) to
(onDelete)
<button
=> onDelete(movie)}
)
className="btn btn-danger btn-sm"
> Delete
</button>
Now let’s use it
- back to moves.jsx component, after showing message, and before the pagination
component, this where we use it. First import it
import MoviesTable from "./moviesTable";

then use it .
<div className="col">
<p style={{ fontWeight: "bold" }}>
Showing {filtered.length} movies in the Database
</p>

<MoviesTable movies={movies} >
/>

<Pagination
itemsCount={filtered.length}
pageSize={pageSize}
currentPage={currentPage}
> />
</div>

Now we have a grad layout, we high level components like (ListGroup, MoviesTable,
Pagination), all this component are the same level abstraction, we don’t have mixer of
high level component and low level component or absolutely elements, our code is
clean here the content is more clear we don’t have to scroll up and down to see what
is going here. 

Here is full Code. 


Movies.jsx

import React, { Component } from "react"; import Pagination from


"./common/pagination"; import ListGroup from "./common/listGroup";
import _ from "lodash"; import { getMovies } from
"../services/fakeMovieService"; import { getGenres } from
"../services/fakeGenreService"; import { paginte } from
"../utils/paginate"; import MoviesTable from "./moviesTable";

class Movies extends Component


{ state = { movies: [],
genres: [], currentPage: 1,
pageSize: 4,
// defaultSelect: "list-group-item active",
}; componentDidMount() { const genres = [{ name: "All Genres" },
...getGenres()]; this.setState({ movies: getMovies(), genres:
genres }); } handleDelete = (movie) => { const movies =
this.state.movies.filter((m) => m._id !== movie._id);
this.setState({ movies });
}; handlePageChange = (page) => {
this.setState({ currentPage: page });
}; handleGenreSelect = (genre) => {
this.setState({ selectedGenre: genre, currentPage: 1 }); };
render() {
//object destructuring const { length: count }
= this.state.movies; const { pageSize,
currentPage, selectedGenre, movies:
allMovies,
} = this.state;
if (count === 0) return <p>There are No movies in the Database</p>;

const filtered = selectedGenre &&


selectedGenre._id
? allMovies.filter((m) => m.genre._id === selectedGenre._id)
: allMovies;
const movies = paginte(filtered, currentPage, pageSize);
return (
<React.Fragment>
<div className="row">
<div className="col-3">
<ListGroup
items={this.state.genres}
selectedItem={this.state.selectedGenre}

>/>
</div>

<div className="col">
<p style={{ fontWeight: "bold" }}>
Showing {filtered.length} movies in the Database
</p>

<MoviesTable movies={movies} />


<Pagination
itemsCount={filtered.length}
pageSize={pageSize}
currentPage={currentPage}
> />
</div>
</div>
</React.Fragment>
);
}
} export default
Movies;

and this is moviesTable.jsx

import React from "react";


const MoviesTable = (props) => {
const { movies, onDelete } = props;

return (
<table className="table table-hover">
<thead>
<tr>
<th>Title</th>
<th>Genre</th>
<th>Stock</th>
<th>Rate</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{movies.map((movie) => (
<tr key={movie._id}>
<td>{movie.title}</td>
<td>{movie.genre.name}</td>

<td>{movie.numberInStock}</td>
<td>{movie.dailyRentalRate}</td>

<td> <button
=> onDelete(movie)}
className="btn btn-danger btn-sm"
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
);
}; export default
MoviesTable;

End of this exercise


We have build filtering component 

Sorting
In this exercise we will build sorting. We will be adding to each column. When we
click the title of the column all data will be change to ascending or descending
format.
Sorting

Sorting – Raising the sort Event


Now we need to handle the click event of each these table headings and raise the sort
event.
So, first we go to moviesTable.jsx component, open it, because here we have our table.
moviesTable.jsx

import React from "react";

const MoviesTable = (props) => {


const { movies,onDelete, } = props; Object Destructing

return (
<table className="table table-hover">
<thead>
<tr>
<th>Title</th>
<th>Genre</th>
Movies Table
<th>Stock</th>
<th>Rate</th>
<th>Action</th>
</tr>
</thead>

...
</table>
);
};

export default MoviesTable;

1. so, on the top in object destructing, we need to add a new property from
our props object call it (onSort), and this a function reference or a
reference to event handler in the movies component.
const { movies, onDelete, onSort } = props;
2. next we pass our event to table heading (th), go to moves table > tr > th
and pass the event (onSort).

<thead>
<tr>
<th => onSort()}>Title</th>
<th => onSort()}>Genre</th>
<th => onSort()}>Stock</th>
<th => onSort()}>Rate</th>
<th => onSort()}>Action</th>
</tr>
</thead>

We pass an arrow function and call it onSort() function , as argument we


need to specify the name of target property for sorting.

The first one is (title) second one (genre.name) because this a nested
property, the third one is (numbrerInStock) and the last one is
(dailyRentalRate).

<tr>
<th => onSort("title")}>Title</th>
<th => onSort("genre.name")}>Genre</th>
<th => onSort("numberInStock")}>Stock</th>
<th => onSort("dailyRentalRate")}>Rate</th>
<th => onSort()}>Action</th>
</tr>

3. now we need to go back our movies.jsx component, here is our MoviesTable


component.
<MoviesTable movies={movies} />

We need to handle onSort event, we set to (>
<MoviesTable
movies={movies}
> > />

4. now implement this handler, so after the others handlers, let, call it
(handleSort) and pass (path) as the argument, to target property.
handleSort = (path) => {

console.log(path);
};
Now open console and click the title of the table heading, you see get the title data in the
console.

Sorting – implementing sorting


To implementing sorting, we use the same uproje we have use before in this
component, and all these event handler as you can see in movies.jsx component,
whenever we change the page (handlePageChange) or select different genre
(handleGenreSelect) we are update the state and then in render method, we will
figure out what exactly we need to display to the user,
1. So here in (handleSort) method, we need to call (this.setState({})) method,
and we pass property (sortColumn:) we set this to an object with two
property’s (path:path) to path and (order:’asc’) ascending,
handleSort = (path) => {
this.setState({ sortColumn: { path: path, order:
"asc" } }); };

For now, let’s just implement the ascending sort and then will come back
implement the reverse order.
2. So, here is our sorColumn, we should also call this in the (state), so when the page
loads, we know how we sorting our movies. So add this in state .
sortColumn: { path: "title", order: "asc" },

we set path to “title” and order to “asc”, now our title data will be sorted
default .
3. Now to implement sorting on the client, once age we are going to use
(lodash), so import it on the top.
import _ from "lodash";

now, we go to our render method, so here is render method we should do


sorting after the filtering data, first we filter, then we sort, and finally we
paginate the data. This is the order we need to implement these operations.
So, we call ( _.orderBy(filtered, ) ) the first argument is an input the array of
filtered movies, the second argument is an array of property names because
we can sort by multiple Column’s or multiple property’s, here we only have
sort by one column, so we pass (sortColumn.path). ( _.orderBy(filtered,
sortColumn.path)), the third argument is the sort order, so once again we have
an array because we sort by multiple column (sortColumn.order) . (
_.orderBy(filtered, sortColumn.path), sortColumn.order )

_orderBy(filtered, sortColumn.path, sortColumn.order);

This will return a new array called it (const sorted)

const sorted = _orderBy(filtered, sortColumn.path,


sortColumn.order) ;

and then pass that to a paginate function, and change (filtered) to (sorted)
const movies = paginte(sorted, currentPage, pageSize);

we need to pick this (sortColumn.path), from the state, so on the top we already
have object destructing let’s add sortColumn from the state.

const {
pageSize,
currentPage,
selectedGenre,
sortColumn,
movies: allMovies,
} = this.state;

Let’s test the application up to this point. 


So, look our (Title) column movies sorted by their title. Default

if we click (Genre) column, now all the (Action) movies come first then
(Comedy) and etc.

If we click our (stock) all number sorted


Now let’s go and implement the reverse sort order .

Here In handleSort() method, we need to make a small change,


handleSort = (path) => { this.setState({ sortColumn: { path:
path, order: "asc" } });
};

If this (path) we are getting here is same as the path we have in our
(sortColumn) we have should simply reverse sort order, otherwise we should
update the (path) and set the order to “asc”.

First we clone the existing sort column object, so (const sortColumn) and we
create new array and use spread operator (const sortColumn =
{…this.state.sortColumn}), then if sortColumn.path is same as this path we
are getting here, (if sortColumn.path === path) we need to change sort order
sortColumn.order becomes first we check the existing order, if
sortColumn.order equal to ascending then we should set to descending
otherwise we should set to ascending. Otherwise if path is different we
should set the path so (sortColumn.path = path) we set to new path, and
(sortColumn.order) always be ascending whenever we set a new column and
finally we should update the state base on this new sortColumn object.

handleSort = (path) => { const sortColumn = {


...this.state.sortColumn }; if (sortColumn.path === path)
sortColumn.order = sortColumn.order === "asc" ? "desc" : "asc";
else { sortColumn.path = path; sortColumn.order = "asc";
}
this.setState({ sortColumn });
};

Save the changes.

So, look our (Title) column movies sorted by their title by Default.
Now, if we click title again now they are sorted in descending order
Let’s change the sort column the (Genre) to ascending order, click on Genre
column

So our sorting is working.

However, there is problems with this implementation that’s we are going to look
next.

Sorting – Moving the Responsibility

As we told in last section there is problem with this implementation


handleSort = (path) => {
const sortColumn = { ...this.state.sortColumn };
if (sortColumn.path === path)
sortColumn.order = sortColumn.order === "asc" ? "desc" : "asc";
else {
sortColumn.path = path;
sortColumn.order = "asc";
}

this.setState({ sortColumn }); };

The problem is that if we re-use moviesTable.jsx component somewhere else let’s


say in anther page, their when this table raise an event they have duplicate all this
logic for determining sort order they doesn’t make since, technically this logic
belongs to moviesTable component itself, so when it erase the sort event instated
given as (path) that is string, we should gave as a (sortColumn) object so we
know what is the path and the current sort order.

1. So back in to moviesTable.jsx component we should promote this


component from a function to a class component, because we want
to add a method in that class for determining the sort order, so
change your function component to class component.by using (ccs)
command. This is the shortcut way
So, go to the moviesTable.jsx component and do it.

moviesTable.jsx

import React from "react";

class moviesTable extends Component {


render() { const { movies, onDelete, onSort
} = props;

return (
<table className="table table-hover">
<thead>
<tr>
<th => onSort("title")}>Title</th>
<th => onSort("genre.name")}>Genre</th>
<th => onSort("numberInStock")}>Stock</th
>

<th => onSort("dailyRentalRate")}>Rate</t


h>
<th => onSort()}>Action</th>
</tr>
</thead>
<tbody>
{movies.map((movie) => (
<tr key={movie._id}>
<td>{movie.title}</td>
<td>{movie.genre.name}</td>
<td>{movie.numberInStock}</td>
<td>{movie.dailyRentalRate}</td>

<td>
<button
=> onDelete(movie)}
className="btn btn-danger btn-sm"
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default moviesTable;

we need to a small changes here in the code.

We need to replace the (props) to (this.props), because props is not a


parameter here, this is in object destructing.
const { movies, onDelete, onSort } = props;

const { movies, onDelete, onSort } = this.props;

import React from "react";

and also change this, on the top we should import (Component) class

import React, {Component} from "react";

save the changes and check you application .


So the first steps our refactoring was to promote this component from
a function to class component,
Now the second steps
Here we need to add new method in moviesTable.jsx component call
it (raiseSort), it’s take a (path) as agreement, in this method we have
the logic for determining the sort order.
raiseSort = (path) => {
};

So back to our movies.jsx component cut all the code from


handleSort method, except the last line of code (setState () ) method.
And paste in to raiseSort method in the moviesTable.jsx

raiseSort = (path) => { const sortColumn = {


...this.state.sortColumn }; if (sortColumn.path === path)
sortColumn.order = sortColumn.order === "asc" ? "desc" : "asc"
; else
{ sortColumn.path =
path; sortColumn.order =
"asc";
}
};

Make these changes in your code.


Now let’s see, what’s going here, we are cloning ({…
this.state.sortColumn}) we don’t have state here, we get everything
by props, so we should change it to ({…this.props.sortColumn}) .
const sortColumn = { ...this.state.sortColumn };

const sortColumn = { ...this.props.sortColumn };

also in this last line, we need to raise the sort event so


(this.props.onSort(sortColumn)) and we pass the sortColumn object.
raiseSort = (path) => { const sortColumn = {
...this.state.sortColumn }; if (sortColumn.path
=== path)
sortColumn.order = sortColumn.order === "asc" ? "desc" :
"asc"
; else
{ sortColumn.path =
path; sortColumn.order =
"asc";
}
this.props.onSort(sortColumn); };

Next, down, instead calling (onSort) function, we are going to call


this new method (raiseSort).
<thead>
<tr>
<th => onSort("title")}>Title</th>
<th => onSort("genre.name")}>Genre</th>
<th => onSort("numberInStock")}>Stock</th>
<th => onSort("dailyRentalRate")}>Rate</th>
<th => onSort()}>Action</th>
</tr>
</thead>

So let’s change all (onSort) event to (this.raiseSort() )


<tr>
<th => this.raiseSort( "title"
)}>Title</th> => this.raiseSort(
<th this.raiseSort( t "genre.name"
)}>Genre</th> his.raiseSort(
<th this.raiseSort() =>
"numberInStock")}>Stoc k</th> <th >=>
"dailyRentalRate")}>Rate</th>
<th }>Action</th>
</tr>

Now back to movies.jsx component, our handleSort event handler, no


longer take (path) as agreement it take (sortColumn) object, so change
it.
handleSort = (path) => {
this.setState({ sortColumn });
};

handleSort = (sortColumn) => {


this.setState({ sortColumn });
};

And one final step 


Back to our movies.jsx component here in render method, where we
are using our (MoviesTable) component.here we should also pass
(sortColumn={sortColumn}) which we pick from the state.
<MoviesTable movies={movies}
> > />

<MoviesTable
movies={movies}
sortColumn={sortColumn}
> />

And this make prefect since.

Save the changes, and test the application,  , ok all it’s working,
sorting and pagination, selecting genre

Message Sorting

Table

Selecting Genres

Pagination
Now it’s time to clean our code , we will extracting our application, we will be
able to use our code in multiple places, we don’t have to duplicate our code or
logic.

Sorting – Extracting Table Header


In our moivesTable.jsx component, so we have put the responsibility of
determining sort order in our moivesTable , and with this if we use this table in
multiple places, we don’t have to duplicate our logic, that is good improvement,
but if we are going to have the table of (Customers) again we have to duplicate
our logic in component like (CustomerTable), so we need to take this to next
level, we need to extract new component like (tableHeader) , and then we can
reuse that (tableHeader) whenever we have table, we could have table of movies
or customers and more.

1. Here in common folder add new file call it (tableHeader.jsx)

2. We import react on the top (imrc)


import React, { Component } from "react";

3. And then create a class component call it (TableHeader)


class TableHeader extends Component {
render() {
return ( );
}
}
export default TableHeader;

4. Next we back to moviesTable.jsx component, here we need to move


(raisSort) function to TableHeader component, so cut it and paste it in
to new component (TableHeader) here.

import React, { Component } from "react";


class TableHeader extends Component { raiseSort = (path) => {
const sortColumn = { ...this.props.sortColumn }; if
(sortColumn.path === path) sortColumn.order = sortColumn.order
=== "asc" ? "desc" : "asc"; else { sortColumn.path =
path; sortColumn.order = "asc";
}
this.props.onSort(sortColumn);
}; render()
{ return
null;
}
}
export default TableHeader;

5. Down in the render method we should have something like this


(thead > tr > th)

<thead>
<tr>
<th => this.raiseSort("title")}>Title</th>
<th => this.raiseSort("genre.name")}>Genre</th>
<th => this.raiseSort("numberInStock")}>Stock</th>
<th => this.raiseSort("dailyRentalRate")}>Rate</th>
<th => this.raiseSort()}>Action</th>
</tr>
</thead>

Now in order to render this markup this TableHeader component needs to


know about the columns so let’s see what is the interface of this
TableHeader is going to look like, we should get (columns : array) which is
array, also we can see in the raiseSort function
({…this.props.sortColumn}) so we should also pass the (sortColumn)
which is an object and (onSort) which is function
// column: array
// sortColumn: object
// onSort: function

this is the interface our new TableHeader component

now focus the render method

here we going to return (thead> tr> th)


render () {

return ( <thead>
<tr>
<th></th>
</tr>
</thead>
);
}

Now we want to render these (th) element dynamically, so add the expression
{} get {this.props.columns.map(column => <th> </th> )} get each column and map to <th>
element , and here we need to render name of the column each column have
property like (column.label) => {this.props.columns.map(column => <th>{column.label}
</th> )}
render() {
return (
<thead>
<tr>
{this.props.columns.map((column) => (
<th>{column.label}</th>
))}
</tr>
</thead>
);
}

6. And next, here we have (thead > tr >) and a bunch of <th> element, and
we should add this, each th has an (onClick) event here we simple call
(this.raiseSort()) which is one of internal method in this class, and we
should pass the (path) to the target property, so our column a pat
property, (this.raiseSort(column.path)), so let’s add it.

return (
<thead>
<tr>
{this.props.columns.map((column) => (
<th => this.raiseSort(column.path)}>{column.label}</th>
))}
</tr>
</thead>

);

So this our table header. Let’s use this in our moviesTable.jsx component

7. So back to the moviesTable.jsx component, first on the top we need to


import (TableHeader)
import TableHeader from "./common/tableHeader";
next inside of this class create a new property call it (columns = []) note
that I am initializing column here, it doesn’t have part of state because
it’s not going to change throughout lifecycle of this component, so a
simple property is sufficient.

- Each column have two property one is path {path: ‘title’} another is
label {label: ‘Title’} and the title with capital (T), ({path:’title’,
label: ‘Title’})
- Second column is the path is (genre.name) and the label is
(Genre)
- Third one is a path is (numberInStock) and the label is (Stock)
- Next one is the path is (dailyRentalRate) and the label is (Rate)
- We need one column for our Delete button …
columns = [
{ path: "title", label: "Title" },
{ path: "genre.name", label: "Genre" },
{ path: "numberInStock", label: "Stock" },
{ path: "dailyRentalRate", label: "Rate" },
{},
];

So we have our columns

8. Now down, let’s replace this (thead) with our new TableHeader component
<thead>

<tr>
<th => this.raiseSort("title")}>Title</th>
<th => this.raiseSort("genre.name")}>Genre</th>
<th => this.raiseSort("numberInStock")}>Stock</th>
<th => this.raiseSort("dailyRentalRate")}>Rate</th>
<th => this.raiseSort()}>Action</th>
</tr>
</thead>

<TableHeader columns={this.columns} />

We pass to our columns (columns={this.columns}), we should also pass these


(sortColumn={sortColumn}) and ( because these are the interface
of TableHeader component .

Note:- we should take these from props. So let’s add it.


TableHeader component
<TableHeader columns={this.columns}
sortColumn={sortColumn} > />

Props
const { movies, onDelete, sortColumn, onSort } = this.props;

save the changes and test the application


everything is working good, but there is one error
in consol. Our TableHeader component, because (each
child in an array or iterator should have a unique key)

- So go to the tableHeade.jsx component - In the


render method, here in <thead> in the <th>
element add the (key)
<th key={column.path} => this.raiseSort(column.path)}>
{column.label}
</th>

Save the changes and test the application 

Everything is working Good, the (sorting) and more

Message Sorting

Table

Selecting Genres

Pagination

Now our code is more cleanable and readable, we have new


file for Table Header.

See the full Code here.


movies.jsx

import React, { Component } from "react"; import


Pagination from "./common/pagination"; import ListGroup
from "./common/listGroup"; import _ from "lodash";
import { getMovies } from
"../services/fakeMovieService"; import { getGenres }
from "../services/fakeGenreService"; import { paginte }
from "../utils/paginate"; import MoviesTable from
"./moviesTable";
class Movies extends Component { state = {
movies: [], genres: [], currentPage: 1,
pageSize: 4, sortColumn: { path: "title",
order: "asc" },
}; componentDidMount() { const genres = [{ _id: "", name: "All
Genres" }, ...getGenres()]; this.setState({ movies: getMovies(),
genres: genres }); } handleDelete = (movie) => { const movies
= this.state.movies.filter((m) => m._id !== movie._id);
this.setState({ movies });
}; handlePageChange =
(page) => {
this.setState({ currentPage: page });
}; handleGenreSelect =
(genre) => {
this.setState({ selectedGenre: genre, currentPage:
1 }); };
handleSort = (sortColumn) => {
this.setState({ sortColumn });
};
render() {
//object destructuring const { length:
count } = this.state.movies; const
{ pageSize, currentPage,
selectedGenre, sortColumn, movies:
allMovies,

} = this.state;
if (count === 0) return <p>There are No movies in the Database</p>;
const filtered = selectedGenre &&
selectedGenre._id
? allMovies.filter((m) => m.genre._id === selectedGenre._id)
: allMovies;
const sorted = _.orderBy(filtered, sortColumn.path, sortColumn.order);
const movies = paginte(sorted, currentPage, pageSize);
return (
<React.Fragment>
<div className="row">
<div className="col-3">
<ListGroup
items={this.state.genres}
selectedItem={this.state.selectedGenre}
/>
</div>

<div className="col">
<p style={{ fontWeight: "bold" }}>
Showing {filtered.length} movies in the Database
</p>

<MoviesTable
movies={movies}
sortColumn={sortColumn}
> > />

<Pagination
itemsCount={filtered.length}
pageSize={pageSize}
currentPage={currentPage}
/>
</div>
</div>
</React.Fragment>
);
}
}
export default Movies;

moviesTable.jsx

import React, { Component } from "react";


import TableHeader from "./common/tableHeader";
class MoviesTable extends Component
{ columns = [
{ path: "title", label: "Title" },
{ path: "genre.name", label: "Genre" },
{ path: "numberInStock", label: "Stock" },
{ path: "dailyRentalRate", label: "Rate" },
{ path: "", label: "Action" },
]; render() { const { movies, onDelete, sortColumn,
onSort } = this.props;
return
(
<table className="table table-hover">
<TableHeader
columns={this.columns}
sortColumn={sortColumn}
> />
<tbody>
{movies.map((movie) => (
<tr key={movie._id}>
<td>{movie.title}</td>
<td>{movie.genre.name}</td>
<td>{movie.numberInStock}</td>
<td>{movie.dailyRentalRate}</td>

<td> <button
=> onDelete(movie)}
className="btn btn-danger btn-sm"
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
);
}
} export default
MoviesTable;

tableHeader.jsx > in common folder

import React, { Component } from "react";


class TableHeader extends Component
{
// column : array
// sortColumn: objcet
// onSort: function
raiseSort = (path) => { const sortColumn = {
...this.props.sortColumn }; if (sortColumn.path === path)
sortColumn.order = sortColumn.order === "asc" ? "desc" : "asc";
else { sortColumn.path = path; sortColumn.order = "asc";
}
this.props.onSort(sortColumn);
};
render() {
return (
<thead>
<tr>
{this.props.columns.map((column) => (
<th key={column.path} => this.raiseSort(column.path)}>
{column.label}
</th>
))}
</tr>
</thead>
);
}
} export default
TableHeader;

sorting – Extracting Table Body


so, now in our moviesTable.jsx component in the (render) method we have same issue
that have earlier, here we have this (<table>) element, we have high level component
(TableHeade), but right below that we have this (<tbody>) with all details about
rendering the body of this table, this will be nicer, if similar to this (TableHeader)
component, if we had component like (Table body) <TableBody />

so, let’s go-ahead and create this component

1. Here in common folder add a new file call it (tableBody.jsx)

2. Let’ import (React) on the top (imrc)


import React, { Component } from "react";

3. And create class component call it (TableBody)


class TableBody extends Component {

render() {
return ();
}
}
export default TableBody;

4. Now what should be return in the render() method, so we want (tbody>tr>td)


class TableBody extends Component {

render() {
return (
<body>
<tbody>
<tr>
<td></td>
</tr>
</tbody>
</body>
);
}
}

We want to render <tr> dynamically base on the number of object we want to


displaying this table, so this where we defining the interface for this
component.
5. Back to the moviesTable.jsx component, first import it (TableBody)
import TableBody from "./common/tableBody";

now call the component (TableBody) after the (TableHeader) component.


<TableBody />

What data do we need to pass to this component (TableBody) the list of


movies, so let’s say data attribute to the movies (data ={movies}),
<TableBody data={movies} />

Note that I name this attribute (data) instate (movies), because I want this
component completely decouple from movies, it doesn’t know anything
about movies, in the future we can re-use this to display a list of customers.
6. So back to tableBody.jsx component, so in the render method on the top, just
like before convention, we do object destructuring, we pick all the property’s
we need in this case (data) from props (const {data} = this.props )
class TableBody extends Component {
render() {

const { data } = this.props;


return (
<body>
<tr>
<td></td>
</tr>
</body>
);
}

So let’s add an expiration we get the (data) and (map() ) it we get an (item)
each item goes to a (tr) like this

class TableBody extends Component {


render() {
const { data } = this.props;
return (
<body>
{data.map((item) => (
<tr>
<td></td>
</tr>
))}
</body>
);
}
}

Now we need to render the (td) dynamically based on the number of columns,
so this table body also know about the columns, so I am gone add the
(columns) in the object destructuring, so add it
const { data,columns } = this.props;

and also add it to (TableBody) component (columns={this.columns})


<TableBody columns={this.columns} data={movies} />

next we add the expiration, we get the (columns) and (map()) each
(columns) to a <td> like this
class TableBody extends Component {
render() {
const { data, columns } = this.props;
return (
<body>
{data.map((item) => (
<tr>
{columns.map((columns) => (
<td></td>
))}
</tr>
))}
</body>
);
}
}

7. Now we want to render the content of each <td>, here in <td> we want render a
property of this current item, so we get (item[column.path]) (item) is the current
object, we use the bracket [] notation to access the property dynamically, the
property is (column.path), however this only works for simple property’s, if
we dealing with nested property, it doesn’t work, in this application one of
property is (genre.name), so we can’t use the bracket [] notation here instate
we are going to use (lodash).

So import the lodash on the top


import _ from "lodash";

lodash has method call it (_.get() ) we pass our object (item) and then the target
property that can be nested so that is (column.path)

return (
<body>
{data.map((item) => (
<tr>
{columns.map((columns) => (
<td>{_.get(item, column.path)}</td>
))}
</tr>
))}
</body>
);

Back to the moviesTable.jsx component, the <tbody> section, select all the
section and comment it.

{/* <tbody>
{movies.map((movie) => (
<tr key={movie._id}>
<td>{movie.title}</td>
<td>{movie.genre.name}</td>
<td>{movie.numberInStock}</td>
<td>{movie.dailyRentalRate}</td>

<td> <button
=> onDelete(movie)} className="btn btn-
danger btn-sm"
>
Delete
</button>
</td>
</tr>
))}
</tbody> */}

Now let’s test our application 

so, here in
browseryou can see the body of the table render properly, but we
Delete
don’t have the ( ) button here.

We successfully render the first four (4) column, now let’s look at the Delete
button column.

8. So back to the moviesTable.jsx component, and let’s uncomment the this piece
of code.
{/* <tbody>
{movies.map((movie) => (
<tr key={movie._id}>
<td>{movie.title}</td>
<td>{movie.genre.name}</td>
<td>{movie.numberInStock}</td>
<td>{movie.dailyRentalRate}</td>

<td> <button
=> onDelete(movie)} className="btn btn-
danger btn-sm"
>
Delete
</button>
</td>
</tr>
))}
</tbody> */}

<tbody>
{movies.map((movie) => (
<tr key={movie._id}>
<td>{movie.title}</td>
<td>{movie.genre.name}</td>
<td>{movie.numberInStock}</td>
<td>{movie.dailyRentalRate}</td>

<td>
<button
=> onDelete(movie)}
className="btn btn-danger btn-sm" >
Delete
</button>
</td>
</tr>
))}
</tbody>

Here we have a (Delete) button column


<button => onDelete(movie)}
className="btn btn-danger btn-sm"
> Delete
</button>

Earlier we learn that these jsx expirations get compile to react element which
are plane JavaScript object. As an example
Const x = <h2> </h2>;
You know that this is React element, which is a plane JavaScript object, the
same is true when we have another component.
So cut the delete button and paste it in the columns array, and we added a new
property call it (content) and set to js6 expirations like this.

columns = [
{ path: "title", label: "Title" },
{ path: "genre.name", label: "Genre" },
{ path: "numberInStock", label: "Stock" },
{ path: "dailyRentalRate", label: "Rate" },
{
label: "Action",
key: "Delete",
content:(
<button
=> this.props.onDelete(movie)}
className="btn btn-danger btn-sm"
>
Delete
</button>
),
},
];

Now we can delete <tbody> element this is no longer use so delete it.

<tbody>

{movies.map((movie) => (
<tr key={movie._id}>
<td>{movie.title}</td>
<td>{movie.genre.name}</td>
<td>{movie.numberInStock}</td>
<td>{movie.dailyRentalRate}</td>

<td> <button
=> onDelete(movie)}
className="btn btn-danger btn-sm"
>
Delete
</button>
</td>
</tr>
))} </tbody>

So back to the browser we have a few errors in (moviesTable.jsx)


component (movie) is not define.

So here in moviesTable.jsx where we defining our columns, here we


should replace this value delete button to a function so instead of to react
element, we set this to a function that takes a parameter like (movie) and
returns react element.
columns = [
{ path: "title", label: "Title" },
{ path: "genre.name", label: "Genre" },
{ path: "numberInStock", label: "Stock" },
{ path: "dailyRentalRate", label: "Rate" },
{
label: "Action",
key: "Delete",
content: (movie) => (
<button
=> this.props.onDelete(movie)}
className="btn btn-danger btn-sm"
>
Delete
</button>
),
},
];

Now movie is the parameter that we are passing here, we have same issue
onDelete() what is onDelete() this is the part of props object.
This MoviesTable we have use this in our movies.js component, here we
have MoviesTable component, look we have (onDelete, onSort) these are
the events that this moves events. Instead of onDelete we have change to
this.props.onDelete().
9. Back in to browser we still have warning (uniqe key)

You can see our table is rendering but we can’t see the (Delete Button), we don’t
have the button.
Because in the tableBody.jsx component we are just rendering the property of movies
object.
tableBody.jsx

return (
<body>
{data.map((item) => (
<tr>
{columns.map((columns) => (
<td>{_.get(item, column.path)}</td>
))}
</tr>
))}
</body>
);

So we need to change this, if this (column) have content property we are going to
render that content, which can be delete button, so here we need to have a condition
statement, I would like to extract this ({_.get(item, column.path)}) logic in to a
separate method to clean up this code.

So let’s add a new method here call it (render Cell = ( )) this method takes two
parameter (item, column) .
renderCell = (item, column) => {

};
And here we can check if column that content is exists if it’s define, if it’s truthy, we
are going to call it which is a function so (column.content()) we gave it an argument
which is (item) and we simply return it
Otherwise
If we don’t have this property, we are going to render the property of current items

So cut this ({_.get(item, column.path)) and return in if statement And


finally we call this new method in <td> here
<td>{this.renderCell(item, column)}</td>

tableBody.jsx

import React, { Component } from "react";


import _ from "lodash";
class TableBody extends Component
{
renderCell = (item, column) => { if
(column.content) return column.content(item);
return _.get(item,
column.path);
};
render() { const { data, columns
} = this.props;
return
(
<tbody>
{data.map((item) => (
<tr>
{columns.map((column) => (
<td>{this.renderCell(item, column)}</td>
))}
</tr>
))}
</tbody>
);
}
} export default
TableBody;

let’s test the application .

every things is working well  the (sorting, pagination, filtering, deleting, selecting genres )

soring - Unique keys


here in our tableBody.jsx component we are using (map()) method in couple of places one is
rendering the rows.
{data.map((item) => (
<tr>
{columns.map((column) => (
<td>{this.renderCell(item, column)}</td>
))}
</tr>
))}

Here we should give to each row a unique key that is pretty simple, so we set (key={item._id}),
<tr key={item._id}>
We should all so apply a unique key to each cell in <td> so here in this <td key={}> here we can
combine the id of the item we are rendering, with pass to the target property. Example
<td key={item._id + column.path} this only work for the first four column only, for the last
column that we have delete button we don’t have the path property. So we can use the logical (or
|| ) operator and do something like this .
<td key={item._id + (column.path || column.key)}
<tbody>
{data.map((item) => (
<tr key={item._id}>

{columns.map((column) => (
<td key={item._id + (column.path || column.key)}>
{this.renderCell(item, column)}
</td>
))}
</tr>
))}
</tbody>

To clean our code, I am going to make separate method for this logic. Let’s

define a new method call it ( createKey = ( ) )


createKey = (item, column) => { return
item._id + (column.path || column.key); };

<tbody>
{data.map((item) => (
<tr key={item._id}>
{columns.map((column) => (
<td key={this.createKey(item, column)}>
{this.renderCell(item, column)}
</td>
))}
</tr>
))}
</tbody>
Sorting - Adding The Sort Icon
Now we want to add the sort icon to the currently sorted column, so in
tableHeader.jsx component this where we are render the table heading <th>.
return (
<thead>
<tr>
{this.props.columns.map((column) => (
<th key={column.path} => this.raiseSort(column.path)}>
{column.label}
</th>
))}
</tr>
</thead>
);

Right after the column name ({column.label}) add an icon , so here we add an
expression and call it { this.renderSortIcon() } this a new method that we are going
to create. So let’s make it.

renderSortIcon = (column) =>{} we say if this column we are getting here is


different from the current sort column. So we say
(if (column.path !== this.props.sortColumn.path) return null; )

If the column dot path dese not equal this.props.sortColumn.path we don’t want to
render any icon, so we return null.
otherwise this column is sorted so we need to render different icon
depended on the sort order. So we say

(if (this.props.sortColumn.order === ‘asc’ ) return <i className=”fa fa-sort-asc”></i> )


If this.props.sortColumn.order == ‘asc’ ascending we should return an icon like
this an <i> element with couple of classes <i className=”fa fa-sort-asc”>

And finally if null of this conditions valeted to true that’s means this column is
sorted in to descending order . so return descending icon.

(return <I className=”fa fa-sort-desc” />)

Save the changes. Test your application.

End of this exercise


We have build sorting component 

Next we will learn about Routing and Navigation


All code see here.

Movies.jsx
import React, { Component } from "react"; import
Pagination from "./common/pagination"; import ListGroup
from "./common/listGroup"; import _ from "lodash";
import { getMovies } from
"../services/fakeMovieService"; import { getGenres }
from "../services/fakeGenreService"; import { paginte }
from "../utils/paginate"; import MoviesTable from
"./moviesTable";
class Movies extends Component { state = {
movies: [], genres: [], currentPage: 1,
pageSize: 4, sortColumn: { path: "title",
order: "asc" },
}; componentDidMount() { const genres = [{ _id: "", name: "All
Genres" }, ...getGenres()]; this.setState({ movies: getMovies(),
genres: genres }); } handleDelete = (movie) => { const movies
= this.state.movies.filter((m) => m._id !== movie._id);
this.setState({ movies });
}; handlePageChange =
(page) => {
this.setState({ currentPage: page });
};
handleGenreSelect = (genre) => {
this.setState({ selectedGenre: genre, currentPage:
1 }); }; handleSort = (sortColumn) => {
this.setState({ sortColumn });
};
render() {
//object destructuring
const { length: count } =
this.state.movies; const { pageSize,
currentPage, selectedGenre,
sortColumn, movies: allMovies,
} = this.state;

if (count === 0)
return (
<p className="alert alert-danger">
There are No movies in the Database
</p>
);
const filtered =
selectedGenre && selectedGenre._id
? allMovies.filter((m) => m.genre._id === selectedGenre._id)
: allMovies;
const sorted = _.orderBy(filtered, sortColumn.path,
sortColumn.order);
const movies = paginte(sorted, currentPage,
pageSize);
return
(
<React.Fragment>
<div className="row">
<div className="col-3">
<ListGroup
items={this.state.genres}
selectedItem={this.state.selectedGenre}
>currentPage={currentPage}
firstItems={this.handlefirstItems}
/>
</div>

<div className="col ">


<p className="alert alert-success" style={{ fontWeight: "bold" }}>
Showing{" "}
<sapn className="badge badge-danger">{filtered.length}</sapn>{" "}
movies in the Database
</p>

<MoviesTable
movies={movies}
sortColumn={sortColumn}
> > />

<Pagination
itemsCount={filtered.length}
pageSize={pageSize}
currentPage={currentPage}
> />
</div>
</div>
</React.Fragment>
);

}
} export default Movies;
moviesTable.jsx

import React, { Component } from "react";


import TableBody from "./common/tableBody";
import TableHeader from "./common/tableHeader";
class MoviesTable extends Component
{ columns = [
{ path: "title", label: "Title" },
{ path: "genre.name", label: "Genre" },
{ path: "numberInStock", label: "Stock" },
{ path: "dailyRentalRate", label: "Rate" },
{ label:
"Action", key:
"Delete", content:
(movie) => (
<button =>
this.props.onDelete(movie)}
className="btn btn-danger btn-sm"
>
Delete
</button>
),
},
]; render() { const { movies, sortColumn,
onSort } = this.props;

return (
<table className="table table-hover">
<TableHeader
columns={this.columns}
sortColumn={sortColumn}
> />

<TableBody columns={this.columns} data={movies} />


</table>
);
} }
export default MoviesTable;

in common folder we have these files. listGroup.jsx

import { times } from "lodash";


import React from "react";
const ListGroup = (props) => { //object destructuring const { items,
textProperty, valueProperty, selectedItem, onItemSelect } = props;
return (
<ul className="list-group">
{items.map((item) => (
<li =>
onItemSelect(item)}
key={item[valueProperty]}
className={ item ===
selectedItem
? "list-group-item active"
: "list-group-item "
}
iD="firstItem"
>
{item[textProperty]}
</li>
))}
</ul>
);
};

//default props
ListGroup.defaultProps =
{ textProperty: "name",
valueProperty: "_id",
};
export default ListGroup;

pagination.jsx

import React from "react"; import


PropTypes from "prop-types";
import _ from "lodash";
const Pagination = (props) =>
{
const { itemsCount, pageSize, currentPage, onPageChange } = props;

const pagesCount = Math.ceil(itemsCount /


pageSize); if (pagesCount === 1) return null;
const pages = _.range(1, pagesCount + 1);
return
(
<nav>
<ul className="pagination">
{pages.map((page) => (
<li key={page} className={page ===
currentPage ? "page-item active" : "page-item"} >
<a className="page-link" => onPageChange(page)}>
{page}
</a>
</li>
))}
</ul>
</nav>
);
};

Pagination.propTypes = { itemsCount:
PropTypes.number.isRequired, pageSize:
PropTypes.number.isRequired,
currentPage: PropTypes.number.isRequired,
onPageChange: PropTypes.func.isRequired,
}; export default
Pagination;

tablebody.jsx

import React, { Component } from "react";


import _ from "lodash";

class TableBody extends Component {


renderCell = (item, column) => {
if (column.content) return column.content(item);
return _.get(item,
column.path);
};
createKey = (item, column) => { return
item._id + (column.path || column.key);
}; render() { const { data,
columns } = this.props; return (
<tbody>

{data.map((item) => (
<tr key={item._id}>
{columns.map((column) => (
<td key={this.createKey(item, column)}>
{this.renderCell(item, column)}
</td>
))}
</tr>
))}
</tbody>
);
}
} export default
TableBody;

tableHeader.jsx

import React, { Component } from "react";


class TableHeader extends Component
{
// column : array
// sortColumn: objcet
// onSort: function
raiseSort = (path) => { const sortColumn = {
...this.props.sortColumn }; if (sortColumn.path === path)
sortColumn.order = sortColumn.order === "asc" ? "desc" : "asc";
else { sortColumn.path = path; sortColumn.order = "asc";
}
this.props.onSort(sortColumn);
};
renderSortIcon = (column) => {
const { sortColumn } = this.props;

if (column.path !== sortColumn.path) return null; if


(sortColumn.order === "asc") return <i className="fa fa-sort-asc" />;
return <i className="fa fa-sort-desc" />;
};
render() {
return (
<thead>
<tr>
{this.props.columns.map((column) => (
<th
className="clickable"
key={column.path}
=> this.raiseSort(column.path)}
>
{column.label} {this.renderSortIcon(column)}
</th>
))}
</tr>
</thead>
);
}
} export default
TableHeader;

in utils folder we have this files

paginate.jsx

import _ from "lodash";


export function paginte(items, pageNumber, pageSize)
{ const startIndex = (pageNumber - 1) * pageSize;
return
_(items).slice(startIndex).take(pageSize).value(); }

This is app.js file


App.js

import React, { Component } from "react";


import Movies from "./components/movies";

class App extends Component


{ render() { return (
<main className="container">
<Movies />
</main>
);
} }
export default App;
Blank Page 

Routing and Navigation


In this section we are going to take our application to the next level by adding
Routing. We will learn all about Routing and

• Route Parameters
• Query string
• How to Redirect the user?
• Implement Not Found (404) pages
• Nested Routing

Are you ready  so let’s get started.

Setup
Before we get to started I want to download these files from this link
https://github.com/SaidShahAhmadi/router_app . Done.
1- now open this folder, this is very basic project that I had created to help us to
learn about routing in react, so it’s has few very basic components we don’t
want to type all this code by hand, so open this folder in your Code editor
(VSCode).
2- Now open terminal then in terminal inside of this folder run (npm install)
3- Ok when all the packages install then run (npm start)
So this is what you should get

Here are our navigation bar in this section we don’t career about a good design, we
just want to implementing the routing, we should take a user from one page to
another page. 

What is Routing?
In React, routers help to create and navigate between the different URLs that make
up your web application. The allow your user to move between your app’s
components and preserve the user’s state.

Routing is the ability to move between different parts of an application when a user
enters a URL or clicks an element (link, button,icon,image etc) within the application.
Adding Routing
To add routing to application we need to install a library called (React Router Dom).
So install it by using this command in your terminal (npm i react-router-dom)
this is the implementation of react router or dom for browser.
There is also anther implementation call React Router Native and that is use native
mobile application, which is entire different topic from this.

Here are some of the components we need to import from the (react-router-dom)
package before starting to create basic routing.

1- To create the basic route using React Router, first we need to go src/index.js
on the top we need to import a component called
(BrowserRouter) from react-router-dom.

import { BrowserRouter } from "react-router-dom";

2- Now we need to add this <app/> component in BrowserRouter.


<BrowserRouter>
<App />
</BrowserRouter>

So this < BrowserRouter> component grasp the history object in browsers


and pass it down component tree. So anywhere in our component tree we
will be able to use the history object, that’s what this component does.

This component is imported from react-router and it's the global package
that is used to wrap different routes inside it. It uses HTML5 History API for
routing.

3- Next we need to register our routes, we need to tell react what component
should be render base on given URL for that let’s go App.js. App.js

import React, { Component } from "react"; import NavBar


from "./components/navbar"; import Products from
"./components/products"; import Posts from
"./components/posts"; import Home from
"./components/home"; import Dashboard from
"./components/admin/dashboard"; import ProductDetails
from "./components/productDetails"; import NotFound from
"./components/notFound"; import "./App.css";

class App extends Component {


render() { return (
<div>
<NavBar />
</div>
);
}
}
export default App;

so here in <div> we have <NavBar> Navigation bar, let’s imagine we have


a <div> for our content area.
class App extends Component {
render() { return (
<div>
<NavBar />
<div className="content">

</div>
</div>
);
}
}

In this content area we want to render a given component base what we have
current URL, that where we are using route component.
On the top We need to import Route component from React Router Dom.
import { Route } from "react-router-dom";

now we need to register one or more routes.


So, we add the route component <Route/> give it two props one is path=””
and other is component={} .
<Route path="" component="" />

So this <Route/> is a component just like a component we have built so for.


It has attribute and these attribute will be pass as props.

Now let’s give to this path slash product path=”/product” and set the
component to Products component, component={Products}
<Route path="/products" component={Products} />

In the start of project, I have already imported the (Products) component so


we don’t need to import it.
<Route path="/products" component={Products} />

This route component looks at the current URL if we match the path url it
will render the component.
So now we have just one Route for Products let’s duplicate this three times
For posts , admin , home

render() {
return (
<div>
<NavBar />
<div className="content">
<Route path="/products" component={Products} />
<Route path="/posts" component={Posts} />
<Route path="/admin" component={Dashboard} />
<Route path="/" component={Home} />
</div>
</div>
);
}

Save the changes, let’s see what we get.

The Home component render because the current URL matches the pattern
for this route.

If we go to the products, you see our Products component and you can also
see Home component.

I will show u how to fix this problems.


Switch
The matching algorithms that the route component uses check to see if the current
(url) start with path, if so then the component will be render.
That means if we go to (/products) in url, so our products component shows here.

But also if we go to (/products/new) in url again we see our products component


still showing.

because our (url) which is (/products/new)


<Route path="/products" component={Products} />
start with (/products)

for this reason, this Home component


<div className="content">
<Route path="/products" component={Products} />
<Route path="/posts" component={Posts} />
<Route path="/admin" component={Dashboard} />
<Route path="/" component={Home} />
</div>

Is render below our products page because when we head over (/products) our url
start with slash (/). So both these path will be match that’s why both this
component products and home components will be render.

So how can we solve this problem?

1- One solution is to use the (exact) attribute here in home route, this route
will match only if the path exactly is.
<Route path="/" exact component={Home} />

Save the changes and see the application


You can see below the products page we no longer see home page

Now if we go to posts or admin, so our home component will not render.

So using exact one solution

2- Second solution is use <switch> component.

So on the top we should import the Switch component from


reactrouter-dom
import { Route, Switch } from "react-router-dom";
and then we should add our all route inside <switch> component the
switch will run the first child that matches the location.
<div className="content">
<Switch>
<Route path="/products" component={Products} />
<Route path="/posts" component={Posts} />
<Route path="/admin" component={Dashboard} />
<Route path="/" component={Home} />
</Switch>
</div>

So if we head over (/products) this is the first route that will match, the
others route will be ignore.

link
even though we are implementing routing here there is a problem with our
implementation.

Let’s open our browser developer tools and go to the Network tab

Network Tab
This is network tab
Here we have different
Sections. Just select (All)

Here

So now if we go to
Home page Home

Here is our request


You can see in request
Section, we have
Bundle of files.
We have (localhost
Bundle.js, info?, etc)

So the first request is (localhost) to download the (HTML) page and the second
request is (bundle.js) to download which is the combination of all JavaScript code.
Why this is not a big deal in this simple application in large enterprise application,
we don’t want to download these entire bundles in to HTML page every time the
user navigate one page to another page.
Thinking about Gmail, Gmail is super-fast as we click our emails we can quickly
see the content of your email, because the entire page is not reloaded.
We refer our application build this way like Gmail a single page application (SPA)
in single page application when the user navigate from one page to another page
instead of reloading entire page with all its assets, we should only update what we
have in content area.

So in this application how we can fix this issue?


how we can turn this to a single page application (SPA) ?

So when we click on these links we don’t have full reload.


1- We need to go our navbar.js component
2- Import the <link> component at top of page from react-router-dom
import { Link } from "react-router-dom";
save the changes, and check your application.
Refresh your application and open developer tools, go to the network tab and see
when we are click on the products or posts, we are not going to see new request.

Rout Props

props

state

Current we are in products page in chrome developer tools on the React tab
1
(components) we search the (products) component.
Look at props of this component we have (history, location,match,etc) we don’t
pass these props here. So where did they come from. That’s one of things that this
route component does.
<Route path="/" component={Home} />

Props
1- History {} – so use history{} to work with the history object in the browser,
and with that we can sent the user to different page.
2- We also have location {} which represent where the app is now. And here
we different property’s (key, pathname,search)
3- And Finlay we have match{} which content information about how this
URL match the path that we set in route let’s take look .
Again we have property’s like (isExact, params,path,url)
If we want look more information about these properties, so you can read
about them at looking react-route documentation https://reactrouter.com/core
Here you can see the documentation about react router

Passing Props
In the last section we learned that this route component passes three additional
props to this component.

Now a question you may have what if we want to pass additional props to this
component.

If we need to pass additional props to this component.


<Route path="/products" component={Products} />

So instead of component attribute we should use render attribute and we set


this to arrow function () => and here we return our <Products/> component and
then pass any additional props like (sortBy=”newest”)
<Route
path="/products" render={() => <Products
sortBy="newest" />} />

So this arrow function takes no parameter and returns a <Products/> component

Save the changes back in the browser, let’s go to our product page and open
developer tools and search for Products component

You can
See our
New props New props
That listed
Under props

However those others props like History{} and location{} or match{} are
disappear here. To fix this problems. We need to back our render arrow function.
<Route
path="/products" render={() =>
<Products sortBy="newest" />} />

And pass (props) here so react will automatically inject those props here now we
need to pass all those props and addition to this custom props, and we use special
syntax in JS6 here we pass object here and use spread operator here to spread prop
object {…props}.
<Route
path="/products" render={(props) =><Products
sortBy="newest" {...props} />} />

With this syntax all the properties of the props object will be listed.

Save the changes, and back in to the browser .

Now we have those stander props (history, location, match) as will our custom props
(sortBy).

Route Parameters
If we need to pass parameters to our route for example here we have list of
products. Depending what product, I select we should see a different product id in
the URL (products/1), that’s a route parameter.
So here in app.js file. Let’ define a new route for Products Details page, so <Route/>
and pass the path should be something (path="/products/:id") so to define the
parameter we should use colon (:) and let’s set the component to ProductDetails
and this component imported at the top.
<Route path="/products/:id" component={ProductDetails} />

Note: note that this is more specific then the second route that’ why I put that first.
<Switch>
<Route path="/products/:id" component={ProductDetails} />
<Route
path="/products"
render={(props) => <Products sortBy="newest" {...props} />}
/>
<Route path="/posts" component={Posts} />
<Route path="/admin" component={Dashboard} />
<Route path="/" component={Home} />
</Switch>

Save the changes and go to the browser.

Let’s go to the Products page and click on of these products.


Now to check the parameter id go to the developer tools and search for
(ProductDetails) and then click ProductDetails component, you can see down here are
the props and expand match:{} and we have prams:{} look we have this property id
that set to 1 (id: "1").

So we can read the route parameter pass to a component using this match: {}
object.

So let’s go to our productDetails.jsx component here we have basic component with


some event handler don’t worry about this will get this shortly. productDetails.js
import React, { Component } from "react";

class ProductDetails extends Component {


handleSave = () => {
// Navigate to /products
};
render() {
return (
<div>
<h1>Product Details - </h1>
<button > </div>
);
}
}
export default ProductDetails;

in render method I want to render the id of product in the heading.

<h1>Product Details - {this.props.match.params.id} </h1>

Save the changes, back to browser.


Now we can see the product id here.

There is one problem when I click the product link the full page reloading. So let’s
go to our producet.jsx component the reason for this is because here we rendering
<a> tag that’s why we have full page reloading.

- So on the top let’s import Link from react-router-dom


import { Link } from "react-router-dom";
- And then replace the <a> tag to <Link> and we should also change the href
attribute to to attribute.
<a href={`/products/${product.id}`}>{product.name}</a>

<Link to={`/products/${product.id}`}>{product.name}</Link>

Optional Route Parameters


We can also add multiple parameters for example here in posts route, let’s add
couple parameters here like (:year, :month).
<Route path="/posts/:year/:month" component={Posts} />

<Switch>
<Route path="/products/:id" component={ProductDetails} />
<Route path="/products"
render={(props) => <Products sortBy="newest" {...props} />}
/>
<Route path="/posts/:year/:month" component={Posts} />
<Route path="/admin" component={Dashboard} />
<Route path="/" component={Home} />
</Switch>

Now back in the browser and go to the post page, you can see (year and month)
in URL.

But if we remove the (year or month) you can see the Home component
What is the reason ?
Well when we defining parameters in route by default those are require so in this
case our URL did not match this path because we didn’t have a value for the month
parameter, so then the matching continued and the last route match the current
URL that’s why we see the Home Page.

so how can we make this this parameter Optional ? we


need append a question mark (?) to them like this
<Route path="/posts/:year?/:month?" component={Posts} />

This the part of regular expression in JavaScript, in regular expression when we


append a question mark (?) to an expression that means that expression is optional.

Now save the changes and go to posts.jsx component here we functional


component.
import React from "react";

const Posts = () => {


return (
<div>
<h1>Admin Posts</h1>
</div>
);
}; export default
Posts;

we can pass props and read the match object or we can simple destructing here
{match} we get the match object and then we can extract the route parameters, so
let’s render the years here ( {match. params.year} ) and ( {match.params.month} )
import React from "react";

const Posts = ({ match }) => {


return (
<div>
<h1>Posts</h1>
Year: {match.params.year}, Month: {match.params.month}
</div>
);
};
export default Posts;

save the changes go back to the browser.

With these changes we no longer see the Home page component here, we have the
post component as we can see we have value for year and for month.

Query string
In the last section we have learn how to define optional parameters, as
we know query string is what we append to URL using question mark (?)
and here we zero or more parameters.
for example (localhost:3000/posts?sortBy=newest&approved=true) so let’s
imagine we only want to see the approved post and we want these post
to be sorted by newest, now we can apply these query string parameters
on any kind of combination of the post route such as all post or post
give years or a given years and given months these optional parameters
we can always added on the right side.

Now let me show you how to read these parameters in react application.

Now open dev tools go to the react and search for the (Posts) component
and select it. Now, we can see the props (history, location, match) these
objects, query string parameters are in the location objects. Extract it under
the location objects, look it the (search) property, the value of this
property is entire query string
(localhost:3000/posts?sortBy=newest&approved=true)
We don’t want manually read the string.

So here in the terminal install (query string) (npm i query-string@6.1.0)


this is very popular package for working with query string, so install it

Now go to our posts.jsx component so in this functional component we


are destructing props object and getting the match property.

import React from "react"; import


queryString from "query-string";

const Posts = ({ match }) => {


return (
<div>
<h1>Posts</h1>
Year: {match.params.year}, Month: {match.params.month}
</div>
);
};
export default Posts;

we should also add (location) in the object destructing.

Now we are going to get location.search this is a string and we should


pass this string using query string package.
- So on the top import queryString from query-string package.
import queryString from "query-string";

- And let pass this location.search to


queryString.parse(location.search)
queryString.parse(location.search);

and this will give as an object with property base on the parameters in
the query string.

We can store everything in one object or using object destructing.


const result = queryString.parse(location.search);

posts.jsx

import React from "react"; import


queryString from "query-string";
const Posts = ({ match }) => { const result =
queryString.parse(location.search); return (
<div>
<h1>Posts</h1>
Year: {match.params.year}, Month: {match.params.month}
</div>
);
}; export default
Posts;

Redirects
When we go an invalid route such as in URL (localhost:3000/xyz) we will get the
home component.

This desired behavior we want redirect the use to Not-found page but before we
implement that, why we see the Home component here, in app.js file in the home
route just add exact attribute, in this case display the home component only if the
user is in the root of our website.
<Route path="/" exact component={Home} />
Back in the browser.

Now we want to redirect the user to different URL like (/not-found) so here in
app.js .

- In the top we need to import one more component from (react-router-dom)


that component is (Redirect)
import { Route, Switch, Redirect } from "react-router-dom";

with this component we can redirect the user to different URL


- We can redirect the user to=("/not-found") page
<Redirect to="/not-found" />

- Now what do we want to display to the user at this URL. We need to register
a new route with this path and the not-found component.
- Above we add another route set the path to (path="/not-found") and component is
(component={NotFound}) which is already imported.
<Route path="/not-found" component={NotFound} />

<Switch>
<Route path="/products/:id" component={ProductDetails} />
<Route path="/products"
render={(props) => <Products sortBy="newest" {...props} />}
/>
<Route path="/posts/:year?/:month?" component={Posts} />
<Route path="/admin" component={Dashboard} />
<Route path="/not-found" component={NotFound} />
<Route path="/" exact component={Home} />
<Redirect to="/not-found" />
</Switch>

Save the changes, goback to the browser

If we change the URL


To an invalid URL
We get automatically Not
-
Found page

This redirect component has another application some time we want to move
resources in the website from one URL to another one we can use <Redirect/>
component to achieve this.
For example.
<Redirect from="/messages" to="/posts" />

If the current URL match with here in (from="/messages" ) then we will redirect the user to
this component (to="/posts")

Programmatic Navigation
There is time that we want redirect the user when they click the button or submit a Form. Here is
the example. In our products list let’s go to one of the product.
Imagine we have form here and Save button below that form, when the user clicks this button we
want take them back to the product page. This is what we call programmatic Navigation.
How we do this?

Let’s one the react dev tools, search for (ProductDetails) component, we can see the props property
(history,location,match), here let’s look at the history object so this object has bunch of useful method
for navigation such as (goBack,goForward) as will (push,replace).

We will user the (replace ) method to control the navigation

Now to implement Programmatic Navigation let’s go to productDetails.jsx component. Here is


the handler for our save button, to take the user back to the product page we need add code like
this.
In the handleSave () method productDetails.jsx
import React, { Component } from "react";

class ProductDetails extends Component {


handleSave = () => { // Navigate to
/products
this.props.history.replace("/products");
};
render() {
return (
<div>
<h1>Product Details - {this.props.match.params.id} </h1>
<button > </div>
);
}
}
export default ProductDetails;

1
2

Now let’s add the Router to our (Vidly) Application here.


Exercise
Here is the exercise of this section I want to add this bootstrap navigation on the top with three
links (Movies, Customers, Rentals) as part of this exercise we create two very simple component
(Customers and Rentals), if we go to an invalid URL like (xyz) we should redirect to the Not-Found

page, when we go to the rote of this website we should automatically redirect to the movies page.
And I want to modify this table and replace the title that was text in link (Airplane,Die Hard, Get Out,Gone Girl) when
we click this page we should go to a new page that is (Movie Form) and rendering the id of movie that is pass as URL
parameter.

Next we will create a form here and instated of rendering id here.

You might also like