Understanding React - The Simplest Practical Guide To Start Coding in React (Enrique Pablo Molinari)
Understanding React - The Simplest Practical Guide To Start Coding in React (Enrique Pablo Molinari)
React
The Simplest Practical Guide
to Start Coding in React
Includes:
React Hooks and React Router
+ Full Stack Authentication
& Authorization Flow
While the author have used good faith efforts to ensure that the information
and instructions contained in this work are accurate, the author disclaim all
responsibility for errors or omissions, including without limitation responsibility
for damages resulting from the use of or reliance on this work. Use of the
information and instructions contained in this work is at your own risk. If any
code samples or other technology this work contains or describes is subject
to open source licenses or the intellectual property rights of others, it is your
responsibility to ensure that your use thereof complies with such licenses
and/or rights.
Contents
Development Environment 7
I Introduction 8
1 Essential JavaScript Concepts 9
1.1 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.2 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.3 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.4 Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.4.1 A Prototype-Base Language . . . . . . . . . . . . . . . 19
1.5 Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
1.6 The Multiple Meanings of this . . . . . . . . . . . . . . . . . . 24
1.7 Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.8 Single Thread Language . . . . . . . . . . . . . . . . . . . . . 30
1.9 The Promise Object and the async/await Keywords . . . . . . 32
II Understanding React 37
2 Essential React Concepts 38
2.1 React Principles . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.2 Creating a React Project . . . . . . . . . . . . . . . . . . . . . 39
2.3 React Components . . . . . . . . . . . . . . . . . . . . . . . . 40
2.4 Rendering Components . . . . . . . . . . . . . . . . . . . . . . 41
2.4.1 Styling . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
2.5 Props . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
2.6 State . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3
CONTENTS 4
5
What is this book about?
6
Development Environment
There are many development environments out there, and you can choose
the one you are more comfortable with. In any case if you don’t have
a preference, I recommend Visual Studio Code (VS Code). And to be
more productive, especially if you are new to React, I suggest installing
the extension VS Code ES7 React/Redux/React-Native/JS snippets which
provides JavaScript and React snippets. I would also suggest installing
Prettier, which is a JavaScript/React code formatter.
To install an extension, in Visual Studio Code, go to the File menu, then
Preferences and then Extensions. You will see a search box that will allow
you to find the extensions that you want to install.
Finally, I really recommend configuring VS Code to format your source
files on save. You can do that by going to the File menu, then Preferences
and then Settings. On the search box type Editor: Format On Save. This
will format your code right after you save it.
7
Part I
Introduction
8
Chapter 1
You can use React just by learning from React docs and you will also be able
to build applications, without digging into JavaScript. However, if you want
to master React and that means, understand why and how certain things
work, you must learn some specific concepts from JavaScript.
In this chapter we will explain those JavaScript concepts and syntactical
constructions needed to make a solid learning path to React. If you want
to dig in more details on some of the topics explained here or others about
JavaScript I recommend to visit the Mozilla[1] web site. Indeed, this section
is based on learning paths and ideas taken from there. Having said that, let’s
begin.
From the Developer Mozilla JavaScript Documentation [1] JavaScript is
defined as:
“JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled
programming language with first-class functions. While it is most well-known
as the scripting language for Web pages, many non-browser environments also
use it, such as Node.js, Apache CouchDB and Adobe Acrobat. JavaScript
is a prototype-based, multi-paradigm, single-threaded, dynamic language,
supporting object-oriented, imperative, and declarative styles.”
9
CHAPTER 1. ESSENTIAL JAVASCRIPT CONCEPTS 10
Let’s then begin learning. Any piece of code written in this chapter will
run using the node interpreter. Install the latest LTS version of Node.js.
Once installed, you can verify that it is working by open a console and type:
$ node -v
That will display the version installed. With this in place, you can execute
a JavaScript file in the following way:
$ node yourjsfile.js
Using Visual Studio Code, you can go to the menu "Terminal" and then
"New Terminal". It will open a small terminal window where you can run
your scripts.
Printing text on the screen must be the very first thing you learn every
time you start playing with a new programming language. This book is not
the exception. You can print text on the screen using the Console object
like the following example:
1 console.log("Coding in React!");
Let’s then open VS Code to try this out. Open a console from your
operating system, create a new folder called ’chapter1’, and then type: code
chapter1. That will open VS Code ready to be used inside of the ’chapter1’
folder. Create a new file called console.js, copy and paste the previous
snippet in that file, save it and execute it typing:
$ node console.js
The object Console was created mainly for debugging purposes, it should
not be used in production. It gives you access to the Browser’s debug console.
It is also not part of the standard, but most modern browsers and nodejs
supports it.
CHAPTER 1. ESSENTIAL JAVASCRIPT CONCEPTS 11
1.1 Variables
Let’s move to variables. You can declare a variable using the let keyword:
1 let myFirstVariable;
Now if you print it you will see the string. You can also declare a variable
using the const keyword:
As you might have guessed, declaring a variable with const will not allow
you to change the value of the variable once it has been initialised. If you do
it the interpreter will throw an error. Try it!.
1.2 Functions
Let’s move now to functions. In the following code snippet we are declaring
a function and after that we are calling it:
1 function saySomething(string) {
2 console.log(string);
3 }
4
5 saySomething("Hello Function!");
CHAPTER 1. ESSENTIAL JAVASCRIPT CONCEPTS 12
If you just need to have a function that gets called as soon as it is declared,
you can use the syntax below. This is called IIFE (Immediately-invoked
Function Expression).
1 (function saySomething(string) {
2 console.log(string);
3 })("Hello Function!");
1 function returnSomething(string) {
2 return "This is it: " + string;
3 }
4
Both say and returnSomething points to the same place which is the
first statement in the body of the function. In the next example, on line
12 we are invoking a function and passing the returnSomething function as
argument. Note how then is invoked on line 8 and its return value returned.
1 function returnSomething(string) {
2 return "This is it: " + string;
CHAPTER 1. ESSENTIAL JAVASCRIPT CONCEPTS 13
3 }
4
1.3 Arrays
Arrays are another very important construction that we will use massively.
This is how you can declare an array:
CHAPTER 1. ESSENTIAL JAVASCRIPT CONCEPTS 14
4 //an array
5 let family = ["Jóse", "Nicolas", "Lucia", "Enrique"];
The elements of an array can be accessed by its index, where the index
of the first element is 0.
1 //an array
2 let family = ["Jóse", "Nicolas", "Lucia", "Enrique"];
3 family[0]; //Jóse
4 family[1]; //Nicolas
5 family[2]; //Lucia
6 family[3]; //Enrique
And if you want to add the elements of an existing array to another array
(empty or not), you can use what is known as spread syntax :
To simply iterate over an array you can use the following for construction:
And in addition we have a set of very useful methods. Let’s see first how
we can iterate over an array using the .forEach method:
3 family.forEach((value) => {
4 //do something with the value here
5 });
Note that we are passing an arrow function to the .filter method with
a condition testing the length of each element in the family array. Those
elements whose length is greater than 5 will be part of the returned new
array. Also note that the family array is not changed at all.
The last method we will see is .map. This method receives a function,
same as the previous two methods, and it will return a new array with the
result of applying the function to every element. It will always return an
array of the same length as the one we are processing. As we will see later,
.map is very used in React to add markup to the elements of arrays.
If you have an array with few elements, instead of working with indexes,
there is a very convenient way called destructuring that allows you to assign
each element of the array to named variables. See below:
1.4 Objects
There are several ways to create objects in JavaScript. We will study those
that will be used further in the book when coding in React. The first way
to create objects that we will see is called Object Literal. An object literal
is created wrapping within curly braces a collection of comma-separated
property:value pairs.
As you can see an object literal can be composed not only of simple
property-value pairs but also for arrays, other objects like address and functions
(called methods). Additionally, since ES6, you can create object literals with
what is called computed property names, like shown below on line 6:
5 surname: "Molinari",
6 [aproperty]: "+54 2920 259031"
7 };
1 console.log(mi.name); //Enrique
2 console.log(mi.sports[0]);//football
3 console.log(mi.address.street);//San Martin
4 console.log(mi.phone);//+54 2920 259031
5 mi.allSports(); //invoke the function and prints the sports array
1 let obj1 = {
2 a: 1,
3 b: 2,
4 };
5 let obj2 = {
6 c: 3,
7 d: 4,
8 };
9 let obj3 = { ...obj1, ...obj2 };
10 //obj3 = { a: 1, b: 2, c: 3, d: 4 }
And if you want to create an object from some declared variables, you
can do this:
1 let a = 1,
2 b = 2;
3 let obj4 = {
CHAPTER 1. ESSENTIAL JAVASCRIPT CONCEPTS 19
4 a,
5 b,
6 };
7 //obj4 = { a: 1, b: 2 }
So far we have seen object literal syntax and what you can do with it.
But what if you don’t know how many objects you will need to create? You
need something like a class from class-based languages like Java or C++.
In JavaScript, we have what is called constructor functions. As we will
see later, JavaScript has added classes to the language, but they are just a
syntactic sugar on top of functions.
A constructor function’s name by convention starts with a capital letter.
Lets see how to create and use them:
As you can see, the function Book looks like a class’s constructor of a
class-based language, in which, in addition, we are able to declare right there
methods like fullName(). We define properties and we initialise them with
the function parameters on lines 2, 3 and 4. On line 5 we define a method.
After that, on lines 10 and 14 we are creating two instances of two different
books and then invoke the fullName() method.
As you can see in the previous example code, the two instances of the Book
constructor function includes, in addition to the property names (and their
values), the function implementation code. Source code is not shared across
the instances like it is in class-based languages. This is an implementation
detail of the language which if you are not aware of it might lead to inefficient
programs. And what about inheritance which is a valuable language resource
used by developers? If there are no classes, do we have inheritance? Yes,
we have. The difference, among others, is that this relation is dynamic,
CHAPTER 1. ESSENTIAL JAVASCRIPT CONCEPTS 21
1 thisBook.fullName();
JavaScript will try first to find the fullName() method in the thisBook
instance. As it is not defined there, JavaScript will then look in their
prototype object and because it’s there it will be called.
Every prototype chain will end up pointing to Object.prototype. So, if
you execute the following:
1 thisBook.valueOf();
JavaScript will try to find the method valueOf() in the thisBook instance.
Then on their prototype and finally on the prototype object of their prototype,
which is Object.prototype. The method is there, so it is called.
Let’s now create a basic inheritance example. We are going to create a
new EBook constructor function that will inherit from Book.
7 //printing eBook:
8 //eBook: EBook {
9 // name: 'Understanding React',
10 // authors: [ 'Enrique Molinari' ],
11 // publishedYear: 2021,
12 // filesize: 2048
13 //}
CHAPTER 1. ESSENTIAL JAVASCRIPT CONCEPTS 23
1 Object.setPrototypeOf(eBook, Book.prototype);
2
1.5 Classes
Yes! JavaScript has classes and their syntax is pretty similar to most of the
class-based languages you might know. They were added to the language in
2015 as part of the EcmaScript 6. The thing is that classes in JavaScript are
a syntactic sugar on top of constructor functions and prototype inheritance.
Behind the scenes, everything works like a prototype-based language, even if
you define instances from classes. That is why it is important to understand
the previous sections.
We will implement our Book and EBook constructor functions with prototype
inheritance from the previous section but using classes.
1 class Book {
2 constructor(name, authors, publishedYear) {
3 this.name = name;
4 this.authors = authors;
5 this.publishedYear = publishedYear;
6 }
7
CHAPTER 1. ESSENTIAL JAVASCRIPT CONCEPTS 24
What we did in the above example with classes, the EBook and Book
inheritance relationship is the same as what we did with the EBook and
Book constructor functions in the section before. See the following code that
demonstrate this:
are some details you should know specially if you use classes for your React
components.
So far, if you use this in constructor functions and classes, and create
the instances using the new keyword, this is bound to the object being
instantiated. However, specifically with constructor functions this won’t work
as expected if you just call the function like in the next example:
1 function Constr(param) {
2 this.param = param;
3 }
4
Calling the constructor function as we are doing on line 5 will bind this
to the window global object. So, this example works perfectly, but what
it does is probably something you don’t expect. This example will end up
adding the param property to the window object and assigning to it the value
2.
On classes, on the other hand, if you need to assign or pass as an argument
a method, that method will lose the binding of this. Let’s study the next
example:
1 class Person {
2 constructor(name) {
3 this.name = name;
4 }
5 saySomething() {
6 console.log(this.name + " is talking...");
7 }
8 }
9 let enrique = new Person("Enrique");
10 enrique.saySomething(); //Enrique is talking...
11
1 class Person {
2 constructor(name) {
3 this.name = name;
4 this.saySomething = this.saySomething.bind(this);
5 }
6 saySomething() {
7 console.log(this.name + " is talking...");
8 }
9 }
10 let enrique = new Person("Enrique");
11 enrique.saySomething(); //Enrique is talking...
12
On line 4 above we are explicitly binding the value of this to the saySomething
method. Inside the constructor the value of this is the object being instantiated.
Now, when saySomething is invoked by the o() call, on line 14, it will work as
expected. Another way to fix this is by declaring methods as arrow functions.
1 class Person {
2 constructor(name) {
3 this.name = name;
4 }
5 saySomething = () => {
6 console.log(this.name + " is talking...");
7 }
8 }
9 let enrique = new Person("Enrique");
10 enrique.saySomething(); //Enrique is talking...
11
Defining the method using the arrow function syntax will work because
arrow functions retain the this value of the enclosing lexical scope which in
this case is the class. However, arrow functions in classes will not get added
to the prototype of the object being instantiated, which means, as we have
already discussed, that every instance will have its own copy of the method.
1.7 Modules
The 6th edition of ECMAScript in 2015 has also added the possibility of
defining modules. Before this release there were other options to create
modular JavaScript programs using tools like RequireJS, among many others.
Now we have this functionality supported natively by modern browsers.
Its use is pretty simple. You can define functions, classes, objects, constants
in a JavaScript file and export those that you want other modules to be used.
Additionally, the client module must import those abstractions that it wants
to use. Let’s see some code examples.
17 print() {
18 console.log("printing: ", this.name);
19 }
20 }
3 function complexThing() {
4 console.log("a complex thing has been executed...");
5 }
6
7 let obj = {
8 a: 1,
9 b: 2,
10 };
11
12 class ASimpleClass {
13 constructor(name) {
14 this.name = name;
15 }
16
17 print() {
18 console.log("printing: ", this.name);
19 }
20 }
21
Everything can be exported at the end just like we are doing on line
22 above. Of course, you don’t have to export everything from a module,
just those abstractions that represent the public API of your module. Let’s
see below how from a module called main-module.js you can import the
abstraction exported by the complex-module.js.
As you can see on line 3 we are importing the three abstractions that the
complex-module.js exports. We are able to use the abstractions imported as
if they were declared in the main-module.js file.
There is a common practice to define a default export abstraction from
a module in order that client modules can import those a bit easier. In the
code below, on line 22, we are exporting the class as our default exported
abstraction from the module.
3 function complexThing() {
4 console.log("a complex thing has been executed...");
5 }
6
7 let obj = {
8 a: 1,
9 b: 2,
10 };
11
12 class ASimpleClass {
13 constructor(name) {
14 this.name = name;
15 }
16
17 print() {
18 console.log("printing: ", this.name);
19 }
20 }
21
On line 3 below, note how we are importing the default exported abstraction
with a different name (it is an alternative, but we can use the same name).
Note also that there are no curly braces.
3 setTimeout(() => {
4 console.log("callback");
5 }, 1000);
6
7 console.log("finishing");
Below we can see the sequence of tasks to execute of the program above:
1. Statement on line 1 is pushed on to the call stack and executed. "starting"
is printed on the console.
CHAPTER 1. ESSENTIAL JAVASCRIPT CONCEPTS 31
4. After one second elapsed from the setTimeout function, the callback
arrow function passed as the first argument was then pushed into the
callback queue. Since there are no more statements to be executed on
the call stack, the event loop gets from the top of the callback queue
the arrow function and pushes it into the call stack. Finally it gets
executed. "callback" is printed on the console.
Note that all the callback functions that end up in the callback queue get
executed after the call stack is empty and not before (when the execution of
the program ends with the last statement). To be very clear with this, look
at the example below.
1 console.log("starting");
2
3 setTimeout(() => {
4 console.log("callback");
5 }, 0);
6
7 console.log("finishing");
1 console.log("starting");
2 fetch("https://jsonplaceholder.typicode.com/posts/1")
3 .then((response) => response.json())
4 .then((json) => console.log(json));
5 console.log("finishing");
fetch is delegated to the Web API which performs an ajax call. Once
the server respond, the callback arrow functions are pushed into the callback
CHAPTER 1. ESSENTIAL JAVASCRIPT CONCEPTS 32
queue. The first callback function, on line 3, transforms the response obtained
from the server to json and the next callback function on line 4 prints that
json in the console. Only after printing on the console the text "finishing"
on line 5, is when those callbacks functions are pushed into the call stack and
executed. You can find a more detailed explanation about the JavaScript
interpreter and how it works, in the great talk by Philip Roberts[5].
Suppose that now, something goes wrong with the executor and the
reject function is invoked. Then, it is possible to handle that in the following
way:
During this book, and usually in React we don’t write promises, but we
use them frequently. By using the fetch method to retrieve data from an
external API, we have to deal with a promise. Look at the example below:
1 function fetchPost() {
2 fetch("https://jsonplaceholder.typicode.com/posts/1")
3 .then((response) => response.json())
4 .then((json) => console.log(json));
5 }
6
7 fetchPost();
As you can see on line 2 above, the fetch method returns a promise,
which allows us to call the then method on it, to work with the response
CHAPTER 1. ESSENTIAL JAVASCRIPT CONCEPTS 34
data.
Another way to deal with promises is by using the async and await
keywords. These keywords were added to Javascript on ES2017 allowing
us to write asynchronous code in a synchronous way. Let’s rewrite one of
our previous examples to take advantage of these keywords. First, we will
create a function that returns a promise. Functions that return promises are
(usually) asynchronous:
1 function thePromise() {
2 return new Promise(function (resolve, reject) {
3 //long async operation
4 setTimeout(() => resolve("finished"), 1000);
5 });
6 }
8 testingKeywords();
First note that we have to wrap the calling code in a function. And
that function must be declared async (see line 1). Then, we use the await
keyword before calling the thePromise() function, on line 3. data (the right
hand side of the assignment on line 3) is actually the "finish" string, and
not the promise returned by thePromise() function. Why? because the
execution of the code inside the async function is paused until the promise
is resolved (or rejected). Messages inside the async function are printed on
the console in the same order as the order of the written statements. That
is why we can say that using these keywords allow us to write asynchronous
code that reads like synchronous.
Now we are going to rewrite the fetchPost() function to use these new
keywords. See below:
CHAPTER 1. ESSENTIAL JAVASCRIPT CONCEPTS 35
7 fetchPost();
As you can see, again, we are declaring the function async, and in this case
the fetch call on line 2, is prepended with the await keyword. Prepending
the await keyword to the fetch function means that instead of returning a
Promise object, it returns (if the promises resolve, you can use a try/catch
block to handle errors) a Response object. That allows us to call on line
3 directly to the json() method of the Response object. As that method
returns a Promise, we also prepend the sentence with the await keyword,
giving us a Javascript object that is finally printed on the console.
11 fetchPost();
And finally, async functions returns always a Promise. Suppose the
example below where we have an async function that returns the json response
from a fetch request. And with that, we would like to print it on the console.
If we do it like below (see line 6), we will get printed Promise { <pending> }
on the console.
1 async function fetchPost() {
2 let data = await
,→ fetch("https://jsonplaceholder.typicode.com/posts/1");
CHAPTER 1. ESSENTIAL JAVASCRIPT CONCEPTS 36
6 console.log(fetchPost());
1 fetchPost()
2 .then((data) => console.log(data));
Part II
Understanding React
37
Chapter 2
In this chapter we will learn the core concepts behind react. With different
examples and with a different approach, we mainly follow the learning path
suggested by the official docs[2], which is fantastic.
I really recommend you to see the explanation about react design principles
by Pete Hunt: React - Rethinking Best Practices.
38
CHAPTER 2. ESSENTIAL REACT CONCEPTS 39
When finished, if everything went fine, you will see a message like:
$ cd coding_in_react
$ npm start
That will open your default browser with the URL http://localhost:3000/,
with the starter application running. Let’s now open VS Code to see what
is inside our project folder. To do that, you can type on your console the
following:
$ cd coding_in_react
$ code .
This will open VS Code with the project folder coding_in_react ready
to be used. Let’s have a look at the project folder structure below. There is
a brief explanation of what each item is.
coding_in_react
node_modules This folder contains packages installed by the
Create React App tool.Any package that we install
will end up here.
public This folder contains the index.html file (among others)
which creates the main skeleton layout of your web
application.
src This is the folder where your React source files will reside.
package-lock.json Here you will find the exact version of each
of the packages (and the dependencies of the
packages) that your project depends on.This file
should be changed only by using npm commands.
CHAPTER 2. ESSENTIAL REACT CONCEPTS 40
package.json Here you will find the packages that your project
depends on.This file should be changed only by
using npm commands.
README.md This is a Markdown file that contains documentation
about the project.
In the next section we will start coding in React using this project. I will
refer to the folders and files there when necessary.
In JSX, you can embed any valid JavaScript Expression between curly
braces. As an example, below you can see the variable name declared and
initialised (on line 2) and used inside the JSX syntax (line 6).
4 return (
5 <p>
6 This is <strong>{name}</strong>
7 </p>
8 );
9 }
1 function passingAsArgument(jsx) {
2 return jsx;
CHAPTER 2. ESSENTIAL REACT CONCEPTS 42
3 }
4
14 return jsx;
15 }
As you might have noted, JSX is the tool you use in React to paint the
components you create on the browser. Let’s start writing some code. In
the VS Code project we have created in section 2.2, create a JavaScript file
called src/Person.js with the Person component we have created in section
2.3. Then, open the file src/index.js, delete their content and paste the
following:
6 const root =
,→ ReactDOM.createRoot(document.getElementById("root"));
7 root.render(
8 <React.StrictMode>
9 <Person />
10 </React.StrictMode>
11 );
The src/index.js file is our JavaScript main module (the entry point).
In this main file we call first the ReactDOM.createRoot(document.getElementById("root"))
(line 6 above) passing as argument the DOM element in which a JSX expression
CHAPTER 2. ESSENTIAL REACT CONCEPTS 43
1 <div id="root">
2 <p>This is a <strong>Person</strong> Component</p>
3 </div>
The div element on line 1 above comes from the public/index.html and
the p, the paragraph with the text inside, comes from rendering the Person
component. If you start the application as we explained in the section 2.2,
you will see the results in the browser. You can also inspect the DOM with
the browser’s development tool.
2.4.1 Styling
In this section, we are going to describe how you can add style to your React
components. In the example below we are adding style to a component using
a CSS file. Suppose the following CSS file called StyleDemo.css:
1 .big {
2 background-color: red;
3 height: 200px;
4 width: 200px;
5 font-size: 30px;
6 color: blue;
7 }
8
9 .small {
10 background-color: green;
11 height: 40px;
12 width: 40px;
13 font-size: 14px;
14 color: black;
15 }
1 import "./StyleDemo.css";
2 import React, { Component } from "react";
3
11 );
12 }
13 }
On line 1 we are first importing the CSS file which is located in the same
directory of the component. Note that on lines 8 and 9 we are using the
className attribute to assign specific style classes to div elements, instead
of using the class attribute like in plain HTML. As we have mentioned, JSX
is translated to JavaScript before execution, and class is a reserved word in
JavaScript. That is the primary reason that forces React to use className
in JSX instead of class. To try this example on VS Code, create the files
StyleDemo.css and StyleAComponent.js under the src folder and then on
the src/index.js, paste this:
6 const root =
,→ ReactDOM.createRoot(document.getElementById("root"));
7 root.render(
8 <React.StrictMode>
9 <StyleAComponent />
10 </React.StrictMode>
11 );
12 return (
13 <>
14 <div style={colorBoxStyle}>Hello World!</div>
15 </>
16 );
17 }
18 }
2.5 Props
In React you can pass arguments to components. These arguments are
transformed into a single JavaScript object with a special name: props
(which stands for properties). Below you can see how we use props in the
Person class-based component:
You might note the use of an empty tag <>...</> in the components
definition above. React components must always return an element.
CHAPTER 2. ESSENTIAL REACT CONCEPTS 47
Note that in our Person component, the props object has two properties:
name and surname. In the class-based component we accessed to them using
this.props and in function-based component just use the argument of the
function.
How do you then pass name and surname to the component? You have
to define them as JSX attributes, lets see that below:
6 const root =
,→ ReactDOM.createRoot(document.getElementById("root"));
7 root.render(
8 <React.StrictMode>
9 <Person name="Enrique" surname="Molinari" />
10 </React.StrictMode>
11 );
6 const root =
,→ ReactDOM.createRoot(document.getElementById("root"));
7 root.render(
8 <React.StrictMode>
9 <StyleAComponent square="small" />
CHAPTER 2. ESSENTIAL REACT CONCEPTS 48
1 import "./StyleDemo.css";
2 import React, { Component } from "react";
3
9 render() {
10 return (
11 <>
12 <div className={this.props.square}>Hello
,→ World!</div>
13 </>
14 );
15 }
16 }
Note that now the StyleAComponent renders a single div element and
the value of the className attribute is obtained from a prop called square.
Additionally, if you define a constructor in a class-based component, like
we did before on line 5, the constructor must receive props as argument and
the first statement must be super(props).
2.6 State
Suppose that we want to build a CountDownLatch component. Starting from
a positive integer, on each second it is decremented until it arrives at zero.
Using what we have learned so far, we can create the component below:
5 const root =
,→ ReactDOM.createRoot(document.getElementById("root"));
6
7 function countDown(number) {
8 root.render(
9 <React.StrictMode>
10 <CountDownLatch startFrom={number} />
11 </React.StrictMode>
12 );
13 if (number === 0) {
14 clearInterval(intervalId);
15 }
16 }
17
And that works great. However, in the way we have implemented this,
the logic that does the decrements, update the browser every second and stop
the countdown is outside the component. We can implement our component
in a much better way incorporating the logic inside the component. This will
give us a much more reusable CountDownLatch component. In order to do
CHAPTER 2. ESSENTIAL REACT CONCEPTS 50
this we must add state to our component. The state in React components
is not like the classic state you know from objects in the object oriented
paradigm. React components react to state changes performing the render
again (also known as re-painted or re-rendered). Let’s see how we add state
to our CountDownLatch component:
1 import React, { Component } from "react";
2
11 render() {
12 return <h1>{this.state.startNumber}</h1>;
13 }
14 }
state is managed and controlled by the component. On line 12, we have
changed the use of props to use the state instead. And on the constructor
starting on line 4, we are initialising the state with a value coming in a
prop. Note that state is a JavaScript object, but it has special meaning for
React. Then on the src/index.js we add the JSX elements to paint our
component on the browser:
1 import ReactDOM from "react-dom/client";
2 import React from "react";
3 import CountDownLatch from "./CountDownLatch";
4
5 const root =
,→ ReactDOM.createRoot(document.getElementById("root"));
6
7 root.render(
8 <React.StrictMode>
9 <CountDownLatch startFrom={10} />
10 </React.StrictMode>
11 );
As you can see on line 9 above we are passing the value 10 as props
which is used to initialise the state in the component’s constructor. So far,
CHAPTER 2. ESSENTIAL REACT CONCEPTS 51
if we run this, we will have just this markup <h1>10</h1> painted on the
browser. Now we have to add the logic that decrements our number. To do
this, we will take advantages from what React calls lifecycle methods. These
methods are present only in class-based components and they are hooks that
you can use to plug your logic. Calling them hooks can be confusing because
React Hooks is a big new topic that we will cover in the next section, but
in object oriented frameworks literature hooks are called to those extension
points that you have to customise the framework. And this is exactly that,
they are a set of methods that React calls at certain moments and you can
use to implement component’s specific logic.
11 componentDidMount() {
12 this.intervalId = setInterval(() => {
13 this.setState((state) => ({
14 startNumber: state.startNumber - 1,
15 }));
16 }, 1000);
17 }
18
19 render() {
20 return <h1>{this.state.startNumber}</h1>;
21 }
22 }
On line 11 we have added the method componentDidMount() where we
CHAPTER 2. ESSENTIAL REACT CONCEPTS 52
use to set up the decrement of the countdown. Note that each second the
arrow function, passed as the first argument of the setInterval function,
that decrements the state.startNumber by one is executed. To update
the state we have to use the special setState() method (line 13 above),
which triggers the re-render of the component. So, on each second the
this.state.startNumber is decremented by one causing the component to
be re-painted on the screen.
11 componentDidMount() {
12 this.intervalId = setInterval(() => {
13 this.setState((state) => ({
14 startNumber: state.startNumber - 1,
15 }));
16 }, 1000);
17 }
18
19 componentDidUpdate() {
20 if (this.state.startNumber === 0) {
21 clearInterval(this.intervalId);
22 }
23 }
24
25 render() {
CHAPTER 2. ESSENTIAL REACT CONCEPTS 53
26 return <h1>{this.state.startNumber}</h1>;
27 }
28 }
5 const root =
,→ ReactDOM.createRoot(document.getElementById("root"));
6
So far, we have seen how to use state and the lifecycle methods to create a
self-contained and reusable component. And we have reviewed how changes
in the component’s state triggers the updating phase which among other
things, re-render the components on the browser. There are some more
lifecycle methods in React. Dan Abramov (from the React’s team) has shared
a picture to summarize all the lifecycle methods available. Note from that
picture that there is a third phase called unmounting. That phase has a
single lifecycle method called componentWillUnmount() which is executed
before the component is unmounted from the DOM. This method is used for
clean up or close resources: subscriptions, sockets, timers, etc.
1
Note that below I’m not using React.StrictMode, this is because there is a strange
bug that when using it causes the componentDidMount being called twice and that makes
our countdown decrements by two each second.
CHAPTER 2. ESSENTIAL REACT CONCEPTS 54
• The constructor is the only place where you can change the state
directly. In all other places use setState(...).
1 //Don't do this
2 this.setState(() => ({
3 startNumber: this.state.startNumber - 1,
4 }));
5
6 //Do this
7 this.setState((state, props) => ({
8 startNumber: state.startNumber - 1,
9 }));
Mounting Updating
Phase Phase
setState()
constructor
render render
componentDidMount componentDidUpdate
Figure 2.1: Order of execution of lifecycle methods used in the
CountDownLatch component
6 function clickmeLink(e) {
7 //this is necesary to prevent the default
8 //link behavior
9 e.preventDefault();
10 console.log("link clicked...");
11 }
12
13 return (
14 <div>
15 {/*events are camelCase*/}
16 {/*and they receive a function not a string*/}
17 <a href="#" onMouseOver={onOver}
,→ onClick={clickmeLink}>
18 click this link
19 </a>
20 </div>
21 );
22 }
Now, lets see how this example can be translated into a class-based
component:
6 this.onOver = this.onOver.bind(this);
7 this.clickmeLink = this.clickmeLink.bind(this);
8 }
9
10 onOver() {
11 console.log("onOver...");
12 }
13
14 clickmeLink(e) {
15 //this is necesary to prevent the default
16 //link behavior
17 e.preventDefault();
18 console.log("link clicked...");
19 }
20
21 render() {
22 return (
23 <div>
24 {/*events are camelCase*/}
25 {/*and they receive a function not a string*/}
26 <a href="#" onMouseOver={this.onOver}
,→ onClick={this.clickmeLink}>
27 click this link
28 </a>
29 </div>
30 );
31 }
32 }
Since event handlers are class methods, on line 26 we can see that they
are passed using this. However, as we have explained in section 1.6, if we
assign or pass as an argument a method, we lose the binding of this, in this
case, to the component instance. That is why we have to explicitly set this
binding as we do on lines 6 and 7. Other than that, it is pretty similar to
what we did on the function-based component.
Here is another example, in this case we change the state on the event
handler:
12 colorChanged(e) {
13 this.setState({
14 value: e.target.value,
15 });
16 }
17
18 render() {
19 const colorBoxStyle = {
20 width: "30px",
21 height: "30px",
22 border: "1px solid rgba(0, 0, 0, 0.05)",
23 backgroundColor: this.state.value,
24 };
25
26 return (
27 <>
28 <label for="colors">Choose your favourite color:
,→ </label>
29 <select name="colors" onChange={this.colorChanged}>
30 <option>Options...</option>
31 <option value="blue">Blue</option>
32 <option value="red">Red</option>
33 <option value="green">Green</option>
34 <option value="yellow">Yellow</option>
35 </select>
36 <div style={colorBoxStyle}></div>
37 </>
38 );
39 }
40 }
In the example above we have a drop down list with colors. When you
CHAPTER 2. ESSENTIAL REACT CONCEPTS 59
choose one color, the onChange event (line 29) is triggered, calling the method
colorChanged() (line 12). The state gets updated with the color selected.
Changing the state makes the render to be executed, painting the square
box (line 36) now colored with the chosen color.
It is important to clarify that React is very efficient in updating the
DOM. Only the exact portion of the DOM that has changed is the one
that is updated. The rest remain untouched. You can check what I’m
saying by running this component inspecting the DOM with the Browser’s
DevTool. This is achieved by React using what is called Virtual DOM and
the reconciliation.
We will look at more events later in the book. Here is the full list of
supported events.
2.8 Hooks
Hooks were added in React 16.8, allowing you to use state and to plug
your code in the lifecycle of your function-based components. Before React
16.8 was released there was no way to use the state in function-based
components. As soon as you needed state you had to move to class-based
components. That seems kind of obvious, right? Functions allocate memory
at call time to store local variables and parameters, but that allocation is
removed after the function returns. On the other hand, classes has state
(instance members) shared across their methods, allocated at instantiation
time and removed, somehow explicitly (C++) or by a garbage collector
at some point. As we explained, in React when the state changes the
component gets re-rendered. With classes, the language gives you the tool
to do this, that is why state is just an instance variable and setState()
ends up calling the render() method to have the component re-rendered.
So, to implement this in function-based components, React had to take care
of maintaining a shared state between function calls. That was a bit of
implementation details. Let’s move to understand how to use hooks and we
will delve into these implementation details later.
Hooks are just functions. Reacts provides some built-in hooks and in this
section we will explain the two most used ones:
• useState(initialState)
• useEffect(callback[, dependencies])
CHAPTER 2. ESSENTIAL REACT CONCEPTS 60
These are the hooks that allow you to have state and lifecycle inside
function-based components.
2.8.1 useState
As mentioned, hooks are special functions that allow you to use React features.
useState(initialState) is the hook that gives you the ability to add
state to a function-based component. To start learning this hook we will
migrate the ColorSelect class-based component from the previous section
to a function-based component.
We first start below importing the useState function from React, and
then call it from inside our component:
6 function colorChanged(e) {
CHAPTER 2. ESSENTIAL REACT CONCEPTS 61
7 setColor(e.target.value);
8 }
9
10 const colorBoxStyle = {
11 width: "30px",
12 height: "30px",
13 border: "1px solid rgba(0, 0, 0, 0.05)",
14 backgroundColor: color,
15 };
16
17 return (
18 <>
19 <label for="colors">Choose your favourite color:
,→ </label>
20 <select name="colors" onChange={colorChanged}>
21 <option>Options...</option>
22 <option value="blue">Blue</option>
23 <option value="red">Red</option>
24 <option value="green">Green</option>
25 <option value="yellow">Yellow</option>
26 </select>
27 <div style={colorBoxStyle}></div>
28 </>
29 );
30 }
You can declare as many state variables as you need. Suppose now that
we want to extend our ColorSelect component, printing how many times
the user changes the color. See below how we have implemented this, adding
another state variable:
1 import { useState } from "react";
2
7 function colorChanged(e) {
8 setChanges((prev) => prev + 1);
9 setColor(e.target.value);
10 }
11
12 const colorBoxStyle = {
13 width: "30px",
14 height: "30px",
15 border: "1px solid rgba(0, 0, 0, 0.05)",
16 backgroundColor: color,
17 };
18
19 return (
20 <>
21 <label for="colors">Choose your favourite color:
,→ </label>
22 <select name="colors" onChange={colorChanged}>
23 <option>Options...</option>
24 <option value="blue">Blue</option>
25 <option value="red">Red</option>
26 <option value="green">Green</option>
27 <option value="yellow">Yellow</option>
28 </select>
29 <div style={colorBoxStyle}></div>
30 <p>Count: {changes}</p>
31 </>
32 );
33 }
On line 5 we have added the declaration of another state variable called
CHAPTER 2. ESSENTIAL REACT CONCEPTS 63
changes. Note that each state variable has its own function to update
it. Then, when the onChange event is triggered, the handler which starts
on line 7, will call the update function of both state variables. The first
one on line 8 increments the number of changes. Note that the setChanges
update function is receiving as an argument a function which as parameter
(prev) receives the current state value, and returns the new calculated value.
This is mandatory when the new state is calculated based on the current
one. As we mentioned in section 2.6 when explaining the state in class-
based components, the mutator function that useState gives you might be
asynchronous too. So, if you need to set a new state value based on the
previous one, it must be passed as parameter as we are doing on line 8 above.
There are two rules you must follow in order to use hooks without problems.
And these rules apply to any hook, not only to the one we are studying in
this section.
• Call hooks at the top level of the React Function: Don’t call them
inside loops or conditions.
• Call hooks from React Functions: Only call hooks from React function-
based components, not from plain JavaScript functions.
By following these rules, you ensure that hooks are called in the same
order each time a component renders, so that React can, among other things,
keep track of the state the component is associated with. This is necessary
as it was explained, there is state being kept between function calls. Some
notes about how this is implemented in React can be found in the official
doc faq.
We will see more complex examples using this hook in the next section.
2.8.2 useEffect
By convention, all hook names start with the use prefix and then what best
describes what the function does or is about. In this case, the term effect
CHAPTER 2. ESSENTIAL REACT CONCEPTS 64
This hook gives you the possibility to attach behaviour to the component
lifecycle, in function-based components, in a similar way you have with class-
based components. As the official documentation explains, this hook can be
seen as the componentDidMount, componentDidUpdate and the componentWillUnmount
combined in one special function.
• useEffect(callback[, dependencies])
callback: the implementation of the side effect.
dependencies: an optional array of props and state variables.
Let’s study our first example below to understand how this hook works:
6 useEffect(() => {
7 console.log("useEffect is called");
8 });
9
10 return (
11 <div>
12 <input
13 type="text"
14 value={state}
15 onChange={(e) => setState(e.target.value)}
16 />
17 </div>
18 );
19 }
CHAPTER 2. ESSENTIAL REACT CONCEPTS 65
On line 6 we call the useEffect function, just passing the first argument,
the callback. In this case, just print on the console once the callback
is executed. Our function-based component also has an state variable,
initialised by calling the useState hook on line 4. And as part of the JSX
that is painted on the browser we have an input text (line 12) where every
time their value changes it will set that value to the state variable (see the
onChange event on line 15). This way of calling useEffect (without passing
any dependency as a second argument) is called by default. In this case,
the callback function will be invoked always after every render (or if you
like, after mount and after update). Note that in this case, every time the
end user type something on the input text, will change the state variable,
triggering the re-render and after that calling to the useEffect callback.
If you need that your callback effect function gets called only on mounting,
as a dependency, second parameter, you have to pass an empty array, like
below. See on line 8:
1 import { useEffect, useState } from "react";
2
6 useEffect(() => {
7 console.log("only invoked on mounting");
8 }, []);
9
10 return (
11 <div>
12 <input
13 type="text"
14 value={state}
15 onChange={(e) => setState(e.target.value)}
16 />
17 </div>
18 );
19 }
Let’s consider another more complex example. Inside the effects callback,
you might require to change the state. In general, this is required to display
data that is fetched from a remote service. In the component example below,
we will display a list of passengers and their number of trips. This information
is retrieved from a service using the native fetch API.
CHAPTER 2. ESSENTIAL REACT CONCEPTS 66
6 useEffect(() => {
7 fetch("https://api.instantwebtools.net/v1/passenger")
8 .then((response) => response.json())
9 .then((pasData) => {
10 setPass({ passengers: pasData });
11 });
12 });
13
14 return (
15 <div>
16 <table>
17 <thead>
18 <tr>
19 <th>name</th>
20 <th>trips</th>
21 </tr>
22 </thead>
23 <tbody>
24 {pass.passengers.data.map((element) => (
25 <tr key={element._id}>
26 <td>{element.name}</td>
27 <td>{element.trips}</td>
28 </tr>
29 ))}
30 </tbody>
31 </table>
32 </div>
33 );
34 }
On line 4 above, we are defining a state variable that will hold the
passengers list, initialised with an object, with an empty array called data.
In the first render, the pass.passengers.data state array is empty, so only
the table header and the structure is displayed on the browser. After that,
the useEffect is executed, fetching the data from a service (line 7), then
CHAPTER 2. ESSENTIAL REACT CONCEPTS 67
transforming the response into json on line 8, and then updating the state
variable with the received data on line 10. The data we get from the service
has the following structure:
1 {
2 "totalPassengers":7365,
3 "totalPages":1,
4 "data":[
5 {
6 "_id":"5f1c59c7fa523c3aa793bea6",
7 "name":"nbmbjkhjhk",
8 "trips":19,
9 },
10 ]
11 }
Let’s see another example. We will improve our component to fetch data
page by page from the service. We will call the same API but now we will
pass the page number that we want to retrieve, and we will also add a button
to retrieve the next page. Let’s study our example below:
7 useEffect(() => {
8 fetch(
9 "https://api.instantwebtools.net/v1/passenger?page=" +
,→ page + "&size=10"
10 )
11 .then((response) => response.json())
12 .then((pasData) => {
13 setState({ passengers: pasData });
14 });
15 }, [page]);
16
17 function handleClick() {
18 setPage((currentPage) => currentPage + 1);
19 }
20
21 return (
22 <div>
23 <table>
24 <thead>
25 <tr>
26 <th>name</th>
27 <th>trips</th>
28 </tr>
29 </thead>
30 <tbody>
31 {state.passengers.data.map((element) => (
32 <tr key={element._id}>
33 <td>{element.name}</td>
34 <td>{element.trips}</td>
35 </tr>
36 ))}
37 </tbody>
38 </table>
39 <button onClick={handleClick}>Next Page >></button>
40 </div>
41 );
42 }
CHAPTER 2. ESSENTIAL REACT CONCEPTS 69
To make the paging work, on line 5 we have defined the page state
variable. The useEffect callback is the same as before with the exception
that now to fetch the data from the API it passes as argument the page
number (line 9). An the other difference is that the dependency argument
of the effect hook (line 15) is: [page]. This means that the effects callback
will be called only if the page state variable changes. And this variable will
change every time we press the "Next Page" button. So, using the dependency
argument of the effect hook is the way you have to control when the effect
hook callback gets called.
So far, we have seen how to use the effects hook to be executed only on
the mounting phase. We have seen that not using the dependencies argument
will make the effect callback to be executed every after render. And finally,
we have seen that by using the dependencies argument we can control when
the effects run after render. Now, we are going to see how to deal with the
unmount phase. This is normally used for cleanup resources, as we have seen
there are some side effects that require cleanup, like timers. To do this, you
have to return a function from the effects callback, like shown below:
1 useEffect(() => {
2 //effects here
3
4 return () => {
5 //clean resources here
6 };
7 }, dependencies);
7 useEffect(() => {
8 if (startNumber === props.startFrom) {
9 intervalId.current = setInterval(() => {
10 setStartNumber((startNumber) => startNumber - 1);
11 }, 1000);
12 }
13 return () => {
14 if (startNumber === 1) {
15 clearInterval(intervalId.current);
16 }
17 };
18 }, [startNumber, props.startFrom]);
19
20 return <h1>{startNumber}</h1>;
21 }
On line 4, we define the startNumber state variable to hold the counter
which is initialised with the props.startFrom. On line 5, we call to the
useRef hook which returns a mutable object with a .current property
initialised in 0. That mutable object is assigned to the intervalId variable
(on line 9), which is required later for the cleanup. The mutable object
returned by the useRef hook can be seen as instance variables like we have
in objects. The initialisation is only done in the first call of the function
component and the value is persistent across the subsequent calls. It is just
an instance variable in a function-based component. Then, on line 7 we
define the useEffect. Note that with the condition on line 8 we are setting
up the timer only in the first call of the component. The return value of the
setInterval is assigned to our intervalId.current "instance" variable.
Then, on line 13 we return a function that will clear the timer only when
the counter value is one (when the countdown is finished). Each second,
the setStartNumber is called decrementing the startNumber state value,
executing the render. And only when the startNumber state value is 1 the
clearInterval is executed, stopping the timer. Confused? Let’s review this
step by step. Suppose we call our CountDownLatch component starting with
3, like below:
1 import ReactDOM from "react-dom/client";
2 import React from "react";
CHAPTER 2. ESSENTIAL REACT CONCEPTS 71
5 const root =
,→ ReactDOM.createRoot(document.getElementById("root"));
6 root.render(<CountDownLatch startFrom={3} />);
On Mounting:
1. startNumber is initialised with 3.
2. render is called, painting 3 on the browser.
3. timer is set up
4. intervalId.current is assigned with the identifier returned from setInterval
5. the cleanup function is not called but somehow remembers that startNumber
== 3.
On the first run of the timer:
1. startNumber is decremented, now the value is 2.
2. render is called, painting 2 on the browser.
3. the cleanup function is called from the previous effects but since startNumber
== 3 clearInterval() is not executed. And somehow remembers the
new value of startNumber.
On the second run of the timer:
1. startNumber is decremented, now the value is 1 (line 10).
2. render is called, painting 1 on the browser.
3. The cleanup function is called from the previous effects but since startNumber
== 2 clearInterval() is not executed. And remembers the new value
of startNumber.
On the third run of the timer:
1. startNumber is decremented, now the value is 0.
2. render is called, painting 0 on the browser.
3. the cleanup function is called from the previous effects (startNumber
== 1). Here the clearInterval is executed, stopping the timer.
I hope that gives you some clarity. In the next chapter we will continue
using these hooks while learning more React concepts.
Part III
Practical React
72
Chapter 3
In this chapter you will learn how to build a simple CRUD application. The
application will support the classic CRUD operations of users data. Along the
implementation you will learn several React concepts and deal with common
problems you will face when coding in React. We will see how to create
and submit forms, display data in tabular format (data grids), open modals
and also very specific React concepts like the children prop and conditional
rendering.
To code the application we will use Material UI[6]. It provides a rich set
of React components, like forms, grids, modals and many more, styled with
the material design theme.
3.1 Material UI
To create this project we have executed the following commands. First
creating the project folder:
$ npx create-react-app react_simple_crud
Then, we install Material UI core, icons and data-grid components:
$ cd react_simple_crud
$ npm install @material-ui/core
$ npm install @material-ui/icons
$ npm install @material-ui/data-grid
73
CHAPTER 3. A SIMPLE CRUD APPLICATION 74
The figure 3.1 illustrates the application’s home page. Observe that it
presents a classic layout with a left panel with menu items (Welcome, User
Lists, Add User), a blue top bar and a center panel (the main page). Figure
3.2 shows a users data grid displayed in the center panel. Each data grid
row is editable and selectable. If you select a row in the grid, you can delete
a user by pressing the "Delete Selected User" button. By double clicking
on a cell you will be able to edit their content and commit the changes by
pressing the Enter key. In addition, clicking on the "More..." button you will
get more details from the user like depicted in the figure 3.3. Finally, we
have the figure 3.4 that shows a form for creating new users.
Now, it is time to decompose the functionality explained into components.
As shown in the figures 3.5, 3.6, 3.7 and 3.8, we have identified the following
components:
App.js
UserDetail.js
Note that we have the App.js root component, which has as children
Layout.js, LeftMenu.js, Welcome.js, UsersList.js and UserForm.js.
UserDetail.js is a child of UsersLists.js. Data in React flows from
the top level component to the bottom one and is one-way. In the next
sections, we will be explaining how each component is implemented. During
this process we will learn new React concepts and typical problems found in
this type of application. We will first start implementing the root component
App.js and with that we will learn the concept called children prop.
CHAPTER 3. A SIMPLE CRUD APPLICATION 76
Figure 3.5: Layout (red), Left Menu (blue) and Welcome (violet) component
CHAPTER 3. A SIMPLE CRUD APPLICATION 80
14 <ChevronLeftIcon />
15 </IconButton>
16 </div>
17 <Divider />
18 {props.left}
19 </Drawer>
20 <main>
21 <Container maxWidth="lg">
22 <Grid item xs={12}>
23 <Paper>{props.children}</Paper>
24 </Grid>
25 </Container>
26 </main>
27 </div>
28 );
29 }
The Layout.js component consists of the blue top bar (see figure 3.1)
painted by the AppBar component on line 6 above. Then, the Left panel
painted by the Drawer on line 11 above, and the Main or center panel
wrapped by the main element on line 20. That will generate the structure
of the layout we see on the mockups above.
Note that on the App.js component, on line 8 we are passing the LeftMenu.js
component as a prop called left to the Layout.js component. This is
used in the Layout.js on line 18. This means that we are rendering the
LeftMenu.js component, that paints the menu options (Welcome, User
Lists and Add User), just there on the Drawer. On the other hand, if you
look one more time at the App.js component above, you will note that the
Welcome.js, UsersList.js and UserForm.js are defined as children of the
Layout.js component. In the Layout.js component source code we can
refer to these components using the special prop called children. This
special prop is used on the highlighted line 23 above. This is how you can
render these three components in the main panel.
In this way, the App.js is rendering the Layout.js plus the LeftMenu.js,
but also, all these three components Welcome.js, UsersList.js and UserForm.js,
one after another. And that will render something like what is shown on
figure 3.9.
In order to avoid this behaviour we have to use what is known as Conditional
Rendering. Conditional rendering isn’t really different than to apply conditions
in Javascript, but in this case to render or not a React component. From
the mockups, we know that depending on the menu item clicked on the left
panel, is the component that we have to render on the main panel. The items
on the menu are the conditions to render or not a specific component.
9 function handleClick(item) {
10 setItemClicked(item);
11 }
12
13 return (
CHAPTER 3. A SIMPLE CRUD APPLICATION 86
14 <Layout
15 left={<LeftMenu items={MENU_ITEMS}
,→ handleMenu={handleClick}
,→ valueItem={itemClicked}/>}>
16 {itemClicked === MENU_ITEMS.WELCOME && <Welcome />}
17 {itemClicked === MENU_ITEMS.USERSLIST && <UsersList
,→ />}
18 {itemClicked === MENU_ITEMS.USERFORM && <UsersForm />}
19 </Layout>
20 );
21 }
On line 2 above we have declared an object with one constant per menu
item (Welcome, User Lists and Add User). Then, on line 7 we have declared
the state variable itemClicked initialised with MENU_ITEMS.WELCOME. And
now on the JSX starting on line 14 we render the components depending
on the value of the itemClicked state variable (lines 16, 17 and 18). As
you can see, due to itemClicked is initialised with MENU_ITEMS.WELCOME,
only the Welcome component is rendered. We are using the logical &&
operator to create conditional expressions, it is a syntactic shortcut that
I like. However, there are other options that you can check in the official
docs about conditional rendering.
Let’s study now how the value of the itemClicked is changed, triggering
the re-render of a different component on the main panel. The itemClicked
is passed as prop (valueItem) to the LeftMenu.js component (line 15 above).
And in addition, we are passing as a prop (handleMenu) a handler function
that changes the value of the itemClicked (function defined on line 9 above).
Let’s see below how the LeftMenu.js component uses these props to change
which component is rendered.
6 return (
7 <List>
8 <ListItem
9 selected={props.valueItem === props.items.WELCOME}
10 button
CHAPTER 3. A SIMPLE CRUD APPLICATION 87
11 onClick={() =>
,→ handleListItemClick(props.items.WELCOME)}>
12 <ListItemIcon>
13 <Home />
14 </ListItemIcon>
15 <ListItemText primary="Welcome" />
16 </ListItem>
17 <ListItem
18 selected={props.valueItem === props.items.USERSLIST}
19 button
20 onClick={() =>
,→ handleListItemClick(props.items.USERSLIST)}>
21 <ListItemIcon>
22 <PeopleIcon />
23 </ListItemIcon>
24 <ListItemText primary="User Lists" />
25 </ListItem>
26 <ListItem
27 selected={props.valueItem === props.items.USERFORM}
28 button
29 onClick={() =>
,→ handleListItemClick(props.items.USERFORM)}>
30 <ListItemIcon>
31 <AddBox />
32 </ListItemIcon>
33 <ListItemText primary="Add User" />
34 </ListItem>
35 </List>
36 );
37 }
The LeftMenu.js component above uses the List Component from Material
UI, to create the menu items you have seen on the mockups above. The
props.valueItem is used on the ListItem component in the selected property
(lines 9, 18 and 27). The selected property is responsible to paint the
background grey color on the item that was recently clicked. On line 2, there
is a handler function being called when clicking on any of the items from the
menu (see the onClick event defined on lines 11, 20 and 29). Note that when
each item is clicked it calls this handler with the appropriate constant value
passed as the item parameter. This calls then the props.handleMenu(item)
which invokes the handled defined on line 9 of the parent component (App.js),
CHAPTER 3. A SIMPLE CRUD APPLICATION 88
which finally sets the new value to the itemClicked state variable. And
as we know, when the state changes, the render is executed, producing the
painting on the screen of the appropriate component on the main panel.
CHAPTER 3. A SIMPLE CRUD APPLICATION 89
Then, when the user clicks on any element of the menu that belongs to
the LeftMenu.js component, the handleListItemClick function is called,
calling the prop.handleMenu callback. That invokes the handleClick handler
function from the App.js parent component. And there we have the child to
parent communication. Then, this change the itemClicked state variable,
CHAPTER 3. A SIMPLE CRUD APPLICATION 91
triggering the re-render of the App.js which triggers the re-render of the
LeftMenu.js and all the other child components in the hierarchy. Observe
that in this second re-render of the LeftMenu.js component and in any
other rendering after that, the props are passed again. In this case, the
itemClicked will have a new value, the one that was clicked by the user.
For the LeftMenu.js this will make the background color of the item clicked,
be changed from white to grey (selected color). It is important to note that
even when the click event is triggered and handled on the LeftMenu.js
component, the grey background of the item clicked is set only after the
App.js component gets re-rendered (because that triggers the re-render of
the LeftMenu.js component passing this new value of the itemClicked by
prop).
We are also able to create custom environment variables. For our example
projects, created in this and next chapter, we need a variable to store the URL
of the APIs that our components will consume. To do that we have created
a file called .env in the root folder of the application (check it on github).
The name of the custom environment variable must start with REACT_APP_.
In our case the variable is named REACT_APP_API_URL.
As a design rule we have defined that the value of this variable must be
referenced only from our root component, which in our case is App.js. And
its value is passed by prop to any component that requires it.
1 export default function App() {
2 const MENU_ITEMS = {
3 WELCOME: 0,
4 USERSLIST: 1,
5 USERFORM: 2,
6 };
7 const [itemClicked, setItemClicked] =
,→ React.useState(MENU_ITEMS.WELCOME);
CHAPTER 3. A SIMPLE CRUD APPLICATION 92
10 function handleClick(item) {
11 setItemClicked(item);
12 }
13
14 return (
15 <Layout
16 left={
17 <LeftMenu
18 items={MENU_ITEMS}
19 handleMenu={handleClick}
20 valueItem={itemClicked}
21 />
22 }
23 >
24 {itemClicked === MENU_ITEMS.WELCOME && <Welcome />}
25 {itemClicked === MENU_ITEMS.USERSLIST && <UsersList
,→ apiUrl={apiUrl} />}
26 {itemClicked === MENU_ITEMS.USERFORM && <UsersForm
,→ apiUrl={apiUrl} />}
27 </Layout>
28 );
29 }
Note that the environment variable is read on line 8 and then is passed
as prop to child components on lines 25 and 26.
this chapter, we will explain the ones that we have used to implement the
application.
6 useEffect(() => {
7 fetchUsers();
8 }, [page, pageSize]);
9
28 const columns = [
29 { field: "id", headerName: "ID", width: 60 },
30 { field: "name", headerName: "Name", width: 180,
,→ editable: true },
CHAPTER 3. A SIMPLE CRUD APPLICATION 94
35 return (
36 <>
37 <div style={{ height: 420, width: "100%" }}>
38 <DataGrid
39 rows={users.result.data}
40 columns={columns}
41 pageSize={pageSize}
42 paginationMode="server"
43 page={page}
44 onPageChange={(params) => {
45 setPage(params.page);
46 }}
47 onPageSizeChange={(params) => {
48 setPageSize(params.pageSize);
49 }}
50 rowsPerPageOptions={[3, 5]}
51 rowCount={parseInt(users.result.total)}
52 />
53 </div>
54 </>
55 );
56 }
After that, we have on line 6 the useEffect hook which fetches from the
server (calling the async fetchUsers() function), the list of users to then
update the users state variable (triggering the re-render). Note that the
effects callback will be invoked when either the page or pageSize change
(dependencies parameter on line 8). If the end user using the grid navigate
to the next or previous page or change the size of the number of rows per
CHAPTER 3. A SIMPLE CRUD APPLICATION 95
Then, we will jump directly to the JSX from the return statement of
the component. The DataGrid component on line 38 is wrapped in a div
element to define its height and width. Then on line 39 and 40 we have the
props to feed the grid with the rows data and the definition of the columns.
The rows is the JSON fetched from a server API, as explained before, and
accessible from users.result.data. Its structure is shown below:
1 [
2 { "id": "1", "name": "Enrique", "username": "emolinari",
,→ "email": "..."},
3 { "id": "2", "name": "Josefina", "username": "jsimini",
,→ "email": "..."},
4 { "id": "3", "name": "Lucía", "username": "lmolinari",
,→ "email": "..."}
CHAPTER 3. A SIMPLE CRUD APPLICATION 96
5 ]
After the rows and columns props on the DataGrid component (line 38),
we have the following (in order):
• pageSize: The number of rows for a page. Note that we set this prop
with our pageSize state variable defined on line 4.
• page: The current displayed page. Note that we set this prop with our
page state variable defined on line 3.
13 let userIdSelected = 0;
14
22 setLoading(true);
23
28 setLoading(false);
29 setAlertMsg("User Deleted Successfully");
30 setShowAlert(true);
31 //refresh the grid data after delete
32 fetchUsers();
33 }
34
CHAPTER 3. A SIMPLE CRUD APPLICATION 98
35 function handleCloseAlert() {
36 setShowAlert(false);
37 }
38
39 return (
40 <>
41 <div style={{ height: 420, width: "100%" }}>
42 <DataGrid
43 ...
44 onRowSelected={(e) => (userIdSelected =
,→ e.data.id)}
45 />
46 </div>
47 <div>
48 <StyledBox component="div">
49 <Button variant="contained" color="primary"
,→ onClick={handleDelete}>
50 {loading && <CircularProgress color="inherit"
,→ size={24} />}
51 {!loading && "Delete Selected User"}
52 </Button>
53 </StyledBox>
54 </div>
55 <Snackbar
56 anchorOrigin={{
57 vertical: "top",
58 horizontal: "center",
59 }}
60 open={showAlert}
61 autoHideDuration={3000}
62 onClose={handleCloseAlert}
63 >
64 <Alert severity="success">{alertMsg}</Alert>
65 </Snackbar>
66 </>
67 );
68 }
We have added some more state variables. On line 9 we have the loading
to handle the spinner functionality while the delete operation is in progress.
Then we have showAlert and alertMsg, both used to display a success
CHAPTER 3. A SIMPLE CRUD APPLICATION 99
Then on the JSX, on the DataGrid component (line 42), we have used
the prop onRowSelected which triggers the callback when a row of the
grid is selected. Note that in this case the callback initialises the variable
userIdSelected (declared on line 13) with the id value of the selected row.
This id (the unique identifier of the user), as we will see next, is sent to the
server when triggering the delete operation.
Next to that, we have the Snackbar component (line 55) used to inform
the user about the success or failure of an operation. In this case, we will
use it to inform the user about the delete and edit operations. Note that
by default the Snackbar is closed (not visible) and this is handled by their
open prop (line 60). This prop uses the value of the showAlert state variable
which is initialised in false. The Snackbar gets closed automatically after
three seconds, by using the prop autoHideDuration (line 61) and also offers
a manual close which when clicked will call to the function passed on the
onClose prop. We are passing the handleCloseAlert function (line 62).
The handleCloseAlert function is defined on line 35 and just changes to
false the showAlert state variable.
When the user presses the "Delete Selected User" button the handleDelete
function on line 15 is called. After a validation that verifies that there is a
row selected (line 16), it first sets the loading state variable to true and
then performs the fetch request (line 24) to delete the selected user. After
that, change to false the loading state variable (which hides the spinner),
then, on lines 29 and 30, sets the alertMsg and showAlert state variable
to make the Snackbar visible to inform the user about the success of the
operation. Finally, to refresh the grid, it performs a call to the fetchUsers
functions (line 32).
Let’s move now to show how the edit operation is implemented. If you
specify in the columns definition the property editable in true, the DataGrid
component when the user double click on a cell of that column, will place
CHAPTER 3. A SIMPLE CRUD APPLICATION 100
the cell value inside an input text, allowing the user to modify that value.
Let see next the UserLists.js component with the elements necessary to
perform the edition of a cell.
5 const columns = [
6 { field: "id", headerName: "ID", width: 60 },
7 { field: "name", headerName: "Name", width: 180,
,→ editable: true },
8 { field: "username", headerName: "UserName", width: 200,
,→ editable: true },
9 { field: "email", headerName: "Email", width: 250,
,→ editable: true },
CHAPTER 3. A SIMPLE CRUD APPLICATION 101
10 ];
11
12 function handleCloseAlert() {
13 setShowAlert(false);
14 }
15
31 return (
32 <>
33 <div style={{ height: 420, width: "100%" }}>
34 <DataGrid
35 ...
36 onEditCellChangeCommitted={(params) =>
,→ handleEditing(params)}
37 />
38 </div>
39 <Snackbar
40 anchorOrigin={{
41 vertical: "top",
42 horizontal: "center",
43 }}
44 open={showAlert}
45 autoHideDuration={3000}
46 onClose={handleCloseAlert}
47 >
48 <Alert severity="success">{alertMsg}</Alert>
49 </Snackbar>
CHAPTER 3. A SIMPLE CRUD APPLICATION 102
50 </>
51 );
52 }
As you can see above, we use the showAlert and alertMsg state variables
to use them in the Snackbar component in the same way as we explained
for the delete operation. After that, on the columns definition starting
on line 5 you can see the editable property which allows, by double click
on a cell, to edit it. On line 36 in the DataGrid component, we use the
onEditCellChangeCommitted prop to pass a callback that is called when
you submit the edition. That submission might be done with the enter or
tab keys. When that occurs, the handleEditing function is called passing
as argument the values of the row where the cell is edited (including the
new value). The function handleEditing starting at line 16, use the fetch
function to consume an API. Note the params argument that receives which
is an object with the fields:values of the row where the cell being edited
belongs. That argument is used on line 17 to create the PUT URL to that
specific user Id and on line 19 to build the body of the request.
5 const columns = [
6 { field: "id", headerName: "ID", width: 60 },
7 { field: "name", headerName: "Name", width: 180,
,→ editable: true },
8 { field: "username", headerName: "UserName", width: 200,
,→ editable: true },
9 { field: "email", headerName: "Email", width: 250,
,→ editable: true },
10 {
11 field: "action",
CHAPTER 3. A SIMPLE CRUD APPLICATION 103
12 headerName: "Action",
13 width: 250,
14 renderCell: (params) => (
15 <Button
16 variant="contained"
17 color="primary"
18 size="small"
19 style={{ marginLeft: 16 }}
20 onClick={() => openDetails(params.row.id)}
21 >
22 More...
23 </Button>
24 ),
25 },
26 ];
27
28 function openDetails(rowId) {
29 setUserId(rowId);
30 setShowDetail(true);
31 }
32
33 function closeDetails() {
34 setShowDetail(false);
35 }
36
37 return (
38 <>
39 <div style={{ height: 420, width: "100%" }}>
40 <DataGrid
41 ...
42 />
43 </div>
44 <div>
45 <StyledBox component="div">
46 ...
47 </StyledBox>
48 </div>
49 <UserDetails
50 apiUrl={props.apiUrl}
51 userId={userId}
52 show={showDetail}
CHAPTER 3. A SIMPLE CRUD APPLICATION 104
53 handleClose={closeDetails}
54 />
55 <Snackbar>
56 ...
57 </Snackbar>
58 </>
59 );
60 }
On the component above, the first thing to note is how we paint the
"More..." button on each row of the grid. To do this, starting on line 14
we have used the renderCell property of the column definition interface
called GridColDef. The callback passed to the renderCell property receives
a parameter (params) of the interface GridCellParams. Additionally, the
button, on line 20, defines an onClick event which calls the openDetails
function passing as an argument the identifier of the user. As we explained
and is illustrated on figure 3.7 the dialog box with the details was written
creating the component UserDetails.js. On line 49 we render this component.
We have a communication from the parent (UserLists.js) to child (UserDetails.js),
by passing via prop the identifier of the user (userId prop on line 51), in
order that the child component can request additional information from that
user. It also passes the showDetail boolean value (a state variable defined
on line 3) that is used to show or hide the dialog box. Note that additionally
we are passing the prop handleClose (line 53) with the handler function
closeDetails. As we see later, that represents our communication from
child to parent.
5 useEffect(() => {
6 if (props.userId <= 0) return;
7
16 return setLoading(true);
17 }, [props.userId]);
18
19 return (
20 <Dialog open={props.show}>
21 <DialogTitle id="alert-dialog-title">User
,→ Details</DialogTitle>
22 <DialogContent>
23 <DialogContentText id="alert-dialog-description">
24 {loading ? (
25 <CircularProgress />
26 ) : (
27 <table>
28 <tbody>
29 <tr>
30 <td>Name: </td>
31 <td>{userData.result.response.name}</td>
32 </tr>
33 <tr>
34 <td>User Name: </td>
35
,→ <td>{userData.result.response.username}</td>
36 </tr>
37 <tr>
38 <td>Website: </td>
CHAPTER 3. A SIMPLE CRUD APPLICATION 106
39
,→ <td>{userData.result.response.website}</td>
40 </tr>
41 <tr>
42 <td>Address: </td>
43 <td>
44 {userData.result.response.address.street
,→ +
45 " - " +
46 userData.result.response.address.city}
47 </td>
48 </tr>
49 </tbody>
50 </table>
51 )}
52 </DialogContentText>
53 </DialogContent>
54 <DialogActions>
55 <Button onClick={props.handleClose} color="primary"
,→ autoFocus>
56 Close
57 </Button>
58 </DialogActions>
59 </Dialog>
60 );
61 }
On line 2, we have defined the userData state variable to store user details
fetched from an API. And on line 3, we have defined the boolean loading
state variable for our spinner.
fresh data.
On line 20 you can see that we use the Dialog component from Material
UI. The prop open is the one used to display or not the dialog box. Finally,
on line 55 we have the close button that defines the onClick event, which
receives the handler function props.handleClose, that belongs to the parent.
That is our child to parent communication, as we previously explained. When
this button is pressed the closeDetails function from the parent is executed
which sets the showDetail state variable to false. Hiding the dialog box.
3.9 Forms
There are two ways to implement forms in React, the controlled way or the
uncontrolled way. In the controlled way, the React component controls the
input’s value of the form by using the state. While in the uncontrolled way
the input’s value is handled by the DOM, like in plain HTML. To implement
the UserForm.js component, we have used the controlled way which is the
one that the official React docs recommend.
As shown by the figure 3.4, the form that we have to build has three input
text. For the input text we use the TextField component from Material UI.
It makes the form validation easier, but the way of managing the happy path
is the same as using the plain HTML input element.
8 function handleSubmit(e) {
9 e.preventDefault();
10
CHAPTER 3. A SIMPLE CRUD APPLICATION 108
11 console.log(e);
12 }
13
14 function handleChange(e) {
15 const name = e.target.name;
16 const value = e.target.value;
17 setInputsValue((inputsValue) => {
18 return { ...inputsValue, [name]: value };
19 });
20 }
21
22 return (
23 <>
24 <form noValidate autoComplete="off"
,→ onSubmit={handleSubmit}>
25 <div className="form">
26 <TextField
27 id="name"
28 name="name"
29 label="Name"
30 fullWidth={true}
31 value={inputsValue.name}
32 onChange={handleChange}
33 />
34 </div>
35 <div className="form">
36 <TextField
37 id="username"
38 name="username"
39 label="User Name"
40 fullWidth={true}
41 value={inputsValue.username}
42 onChange={handleChange}
43 />
44 </div>
45 <div className="form">
46 <TextField
47 id="email"
48 name="email"
49 label="EMail"
50 fullWidth={true}
CHAPTER 3. A SIMPLE CRUD APPLICATION 109
51 value={inputsValue.email}
52 onChange={handleChange}
53 />
54 </div>
55 <div>
56 <Button type="submit" variant="contained"
,→ color="primary">
57 Submit
58 </Button>
59 </div>
60 </form>
61 </>
62 );
63 }
On the JSX above, starting at line 22, we have the form definition with
the three input text using the TextField component. After that, on line 56,
the submit Button. As mentioned, to implement the form in a controlled way,
we have to define a state variable for each of the input text. As a general
rule, for each element of a form that can hold a value, a state variable must
be defined and that value must be kept in sync. The component, at the point
of requiring to read the value of any element of the form, will get it from the
corresponding state variable. The component’s state is the source of trust.
Having said that, on line 2, we have defined the inputsValue state variable,
which is an object with three properties, one for each of the input text.
Now, we have to keep the input value and their corresponding state
variable in sync. We do that by adding to each of the form elements the
onChange event. On lines 32, 42 and 52, you will see the event with the
handleChange handler. On line 14 you can see the handler function defined.
Every event handler in React will receive as a parameter an instance of
SyntheticEvent. It is a wrapper around the browser’s native event. Using
their e.target property we get access to the DOM input element, and from
there we can retrieve their name and their value, like is shown above on
lines 15 and 16. Then, on line 17, we update the state variable. So,
any change on any of the input elements of the form gets copied to their
corresponding state variable. This is how we can keep in sync the value of
the form inputs with the component’s state. Note that the function passed
as argument to the setInputsValue returns an object literal which contains
the current properties and values of the inputsValue object (using spread
syntax, see section 1.4), plus the new property/value that has just changed
CHAPTER 3. A SIMPLE CRUD APPLICATION 110
11 function handleSubmit(e) {
12 e.preventDefault();
13 setLoading(true);
14 setErrorInputs({});
15 fetch(props.apiUrl, {
16 method: "POST",
17 body: JSON.stringify({
18 name: inputsValue.name,
19 userName: inputsValue.username,
20 email: inputsValue.email,
21 }),
22 headers: {
23 "Content-type": "application/json; charset=UTF-8",
24 },
25 })
26 .then((response) => response.json())
27 .then((json) => {
28 setLoading(false);
29 checkResponse(json);
30 });
31 }
32
33 function handleClose() {
34 setShowSuccess(false);
35 }
CHAPTER 3. A SIMPLE CRUD APPLICATION 111
36
37 function handleChange(e) {
38 const name = e.target.name;
39 const value = e.target.value;
40 setInputsValue((inputsValue) => {
41 return { ...inputsValue, [name]: value };
42 });
43 }
44
45 function checkResponse(json) {
46 if (json.name && json.userName && json.email) {
47 setShowSuccess(true);
48 return;
49 }
50 if (!json.name) {
51 setErrorInputs((errorInputs) => ({
52 ...errorInputs,
53 name: "This field is required",
54 }));
55 }
56 if (!json.userName) {
57 setErrorInputs((errorInputs) => ({
58 ...errorInputs,
59 username: "This field is required",
60 }));
61 }
62 if (!json.email) {
63 setErrorInputs((errorInputs) => ({
64 ...errorInputs,
65 email: "This field is required",
66 }));
67 }
68 }
69
70 return (
71 <>
72 <form noValidate autoComplete="off"
,→ onSubmit={handleSubmit}>
73 <div className="form">
74 <TextField
75 id="name"
CHAPTER 3. A SIMPLE CRUD APPLICATION 112
76 name="name"
77 label="Name"
78 error={typeof errorInputs.name !== "undefined"}
79 helperText={errorInputs.name ? errorInputs.name
,→ : ""}
80 required={true}
81 fullWidth={true}
82 value={inputsValue.name}
83 onChange={handleChange}
84 />
85 </div>
86 <div className="form">
87 <TextField
88 id="username"
89 name="username"
90 label="User Name"
91 error={typeof errorInputs.username !==
,→ "undefined"}
92 helperText={errorInputs.username ?
,→ errorInputs.username : ""}
93 required={true}
94 fullWidth={true}
95 value={inputsValue.username}
96 onChange={handleChange}
97 />
98 </div>
99 <div className="form">
100 <TextField
101 id="email"
102 name="email"
103 label="EMail"
104 error={typeof errorInputs.email !== "undefined"}
105 helperText={errorInputs.email ?
,→ errorInputs.email : ""}
106 required={true}
107 fullWidth={true}
108 value={inputsValue.email}
109 onChange={handleChange}
110 />
111 </div>
112 <div>
CHAPTER 3. A SIMPLE CRUD APPLICATION 113
Now, if the user presses the submit button, on line 113, the handleSubmit
function will be invoked. The first statement of the function, on line 12, will
disable the default browser behaviour which is executed when a submission is
done. In this default or native behaviour the browser will perform a request
reloading the page. We don’t want that, as we will perform the request
ourselves using the fetch function. On line 13, we set to true the loading
state variable. That immediately will show a spinner as the value of the
CHAPTER 3. A SIMPLE CRUD APPLICATION 114
submit button. Observe on line 114 the conditional render to show the
spinner or the "Submit" value. On line 14, we initialise (cleaning it from
the previous submission) the errorInputs state variable. Then, on line 15
we invoke the fetch function that performs the request. This is a POST
request, as specified on line 16. On line 17 we define the body of the POST
request, using the JSON.stringify function which transforms an object or
a Javascript value into a JSON string. Note that we get the values from the
inputsValue state variable, and not from the DOM elements.
Finally, after the request finishes, we stop the spinner (on line 28) and
then we inspect the response to verify if there is an error or everything is fine.
That is done in the function checkResponse. As mentioned, we are using
the JSON Placeholder API, which fakes inserts, updates or deletes. It always
responds with success, plus the exact body that you send in the request, plus
the id of the new inserted element. In order to show how forms validation
works in React we will follow the next strategy. If the user does not complete
the three inputs in the form and do the submission, the response will tell us
which input was not completed, and we will use that to inform the user of
the "field required" message, as shown in figure 3.13. That is basically what
the checkResponse function on line 45 does.
provider (maybe it is just another mate on your team or yourself) which will
be the format of the response to identify inputs and their error message.
Creating a Blog
In this chapter we will design and build a blog application that we call
react-blog. The full source code is available on github following the link:
react-blog. To see the react-blog application running, I have written a small
back-end application that can be downloaded and installed from github too,
following the link: blog-api. The blog-api back-end application exposes the
set of APIs required by the react-blog application. They provide the content
for the blog.
To build the react-blog application I have used the html theme called
Editorial from html5up.net[7]. From the original sources I have just removed
JQuery, as we are only interested in the HTML markup and the CSS files.
Let’s start by describing what functionality the blog application will have.
Like in the previous chapter, we will present a set of figures that illustrate
what we are going to build.
116
CHAPTER 4. CREATING A BLOG 117
Then, figure 4.2 shows the full text of the main page of the blog. On
figure 4.3 you can see the full text of a post and finally on figure 4.4 you can
see the search result list.
ByAuthor.js
[
{
"_id":{
"$oid":"..."
},
"title":"...",
"text":"...",
"author":"...",
"date":{
"$date":"..."
}
}
]
[
{
"_id":{
"$oid":"..."
},
"title":"...",
"resume":"...",
"text":"...",
"tags":"...",
"relatedlinks": "...",
"author":"...",
"date":{
"$date":"..."
}
}
]
[
{
"_id":{
"$oid":"..."
},
"title":"...",
"resume":"...",
},
{
"_id":{
"$oid":"..."
CHAPTER 4. CREATING A BLOG 121
},
"title":"...",
"resume":"...",
},
]
[
{
"_id": "...",
"count": 2,
},
{
"_id": "...",
"count": 12,
},
]
[
{
"_id":{
"$oid":"..."
},
"title":"...",
"resume":"...",
"text":"...",
CHAPTER 4. CREATING A BLOG 122
"tags":"...",
"relatedlinks": "...",
"author":"...",
"date":{
"$date":"..."
}
},
]
7 const root =
,→ ReactDOM.createRoot(document.getElementById("root"));
8 root.render(
9 <React.StrictMode>
10 <BrowserRouter>
11 <App />
12 </BrowserRouter>
13 </React.StrictMode>
14 );
With this in place we are ready to start studying the Routes and Route
components. Let’s review how these components can be used on the App.js
root component on the example below:
CHAPTER 4. CREATING A BLOG 123
5 return (
6 <Layout
7 leftPane={<LeftPanel apiUrl={apiUrl} />}
8 mainPane={
9 <>
10 <MainHeader />
11 <Routes>
12 <Route
13 path="/posts/author/:name"
14 element={<Posts apiUrl={apiUrl} />}
15 />
16 <Route path="/posts/:postId" element={<Posts
,→ apiUrl={apiUrl} />} />
17 <Route
18 path="/*"
19 element={<MainContent apiUrl={apiUrl}
,→ pageId={pageId} />}
20 />
21 <Route path="/search/result"
,→ element={<SearchResult />} />
22 </Routes>
23 </>
24 }
25 />
26 );
27 }
As can be seen in the previous example, each route is defined by the
Route component, and it is mandatory to wrap them all using the Routes
component. The path property of the Route component, is compared against
the URL that is currently being navigated. If the URL and the path match,
the component specified in the element property get rendered. The path="/*"
property on line 18 indicates that there are descendant routes, in this case,
routes defined in the MainContent component. Let’s see how that component
looks like:
1 export default function MainContent(props) {
2 const [mainPage, setMainPage] = useState([]);
CHAPTER 4. CREATING A BLOG 124
4 useEffect(() => {
5 fetch(props.apiUrl + "pages/" + props.pageId)
6 .then((response) => response.json())
7 .then((response) => {
8 setMainPage(response);
9 });
10 }, []);
11
12 return (
13 <>
14 <section id="banner">
15 <Routes>
16 <Route index element={<PageSummary page={mainPage}
,→ />} />
17 <Route path="/page/full" element={<PageFullText
,→ page={mainPage} />} />
18 </Routes>
19 </section>
20 <Routes>
21 <Route index element={<LatestPost
,→ apiUrl={props.apiUrl} />} />
22 </Routes>
23 </>
24 );
25 }
4.2.2 Navigation
So far we have studied how to configure the routes to do conditional rendering.
Now, let’s study how to navigate, which means to change the current URL
to make some components to be rendered and others to be removed from the
DOM. This can be done using the Link component. Below is the implementation
of the Menu.js component.
CHAPTER 4. CREATING A BLOG 125
Note on figure 4.5, green box, how this component is rendered on the
browser. We have a homepage link and then the render of the ByAuthor.js
component. When using React Router, links are created by using the Link
component. You can see this on line 9 of the previous example. Link accepts
the property to which is used to specify the route you want to navigate. On
line 9 above, we are navigating to the / route which triggers the rendering
of the MainContent.js. Similar to <Route path>, the <Link to> is relative
too.
It is also possible to send path parameters on the URL and obtain their
values on the rendered components. This is implemented by using a combination
of the Link component, the Route component and the useParams hook. First
it is necessary to define on the Route path property the parameter name and
position. We have already done this on the App.js component. See below
an extraction from that component source code:
On lines 1 and 2 above we have defined the path property which ends
up with a parameter name. These routes are used to render the Posts.js
component which displays blog posts, obtaining them by filtering them by
CHAPTER 4. CREATING A BLOG 126
author or by Id. To use one of these routes, we have to use the Link
component. On the ByAuthor component, illustrated on figure 4.5 (light
green box), we render a list of authors where each item on the list is a link.
Let’s see below how we have implemented this:
1 export default function ByAuthor(props) {
2 const [results, setResults] = useState([]);
3
4 useEffect(() => {
5 fetch(props.apiUrl + "byauthor")
6 .then((response) => response.json())
7 .then((response) => {
8 setResults(response);
9 });
10 }, []);
11
12 return (
13 <>
14 <ul>
15 {results.map((item) => (
16 <li key={item._id}>
17 <Link to={"/posts/author/" + item._id}>
18 {item._id + " (" + item.count + ")"}
19 </Link>
20 </li>
21 ))}
22 </ul>
23 </>
24 );
25 }
This component consumes the /byauthor API, as described in the previous
section, and renders the response as a list. Each item on the list links to the
URL /posts/author/:name. Note that starting on line 15, we are iterating
over the list of authors obtained from the API and generating the value of
the Link to property dynamically.
And finally, we will show how to obtain the path parameter value. As
we showed before from the implementation of the App.js component, the
URL /posts/author/:name renders the Posts.js component passing the name
parameter. Let’s have a look at the source code of the Posts.js component
below:
CHAPTER 4. CREATING A BLOG 127
6 useEffect(() => {
7 let uri = "posts/";
8 if (postId) uri += postId;
9 if (name) uri += "author/" + name;
10
11 fetch(props.apiUrl + uri)
12 .then((response) => response.json())
13 .then((response) => {
14 setPosts(response);
15 });
16 }, [postId, name]);
17
18 return (
19 <span key={name}>
20 {posts.map((post) => (
21 <section key={post._id.$oid}>
22 <header className="main">
23 <h1>{post.title}</h1>
24 </header>
25 <h3>{post.resume}</h3>
26 <p>{post.text}</p>
27 <h4>Related Links</h4>
28 <ul className="alt">
29 {post.relatedlinks.map((link, index) => (
30 <li key={index}>{link}</li>
31 ))}
32 </ul>
33 <h4>Tags</h4>
34 <ul>
35 {post.tags.map((tag, index) => (
36 <li key={index}>{tag}</li>
37 ))}
38 </ul>
39 <h4>Author</h4>
40 <p>{post.author}</p>
41 </section>
CHAPTER 4. CREATING A BLOG 128
42 ))}
43 <p>
44 <Link to="/">Back to Home Page</Link>
45 </p>
46 </span>
47 );
48 }
On lines 3 and 4 we use the useParams hook from React Router to get the
value of the path parameters: postId or name. The Posts.js component
is used to render full text blog posts, but they are obtained by using either
the /posts/:postId API or the /posts/author/:name API, depending on the
parameter received. Note that on lines 7, 8 and 9 we are building the URL
of the API based on the parameter obtained from the path. Once the URL
is built we do the API call on line 11 and the response is rendered.
The SearchBox.js renders the form with the input text. On the submission
event, the /search/{searched text} API is consumed and with that done we
have to somehow navigate to the /search/result URL, passing the response
(the search result response object) as parameter. It is the SearchResult.js
component which renders the results.
5 function handleSubmit(e) {
6 e.preventDefault();
CHAPTER 4. CREATING A BLOG 129
15 function handleChange(e) {
16 setTextSearch(e.target.value);
17 }
18
19 return (
20 <section id="search" className="alt">
21 <form method="get" onSubmit={handleSubmit}>
22 <input type="text" onChange={handleChange}
,→ placeholder="Search" />
23 </form>
24 </section>
25 );
26 }
Now, let’s study how we can obtain the data sent by the navigate
function call, by looking at the implementation of the SearchResult.js
CHAPTER 4. CREATING A BLOG 130
component:
1 export default function SearchResult() {
2 const location = useLocation();
3
4 return (
5 <div className="table-wrapper">
6 <table>
7 <thead>
8 <tr>
9 <th>Author</th>
10 <th>Title</th>
11 <th>Resume</th>
12 <th>Post</th>
13 </tr>
14 </thead>
15 <tbody>
16 {location.state.map((result, index) => (
17 <tr key={index}>
18 <td>{result.author}</td>
19 <td>{result.title}</td>
20 <td>{result.resume}</td>
21 <td>
22 <Link to={"/posts/" + result._id.$oid}>Read
,→ more...</Link>
23 </td>
24 </tr>
25 ))}
26 </tbody>
27 </table>
28 <ul className="actions">
29 <li>
30 <Link to="/">Back To Home Page</Link>
31 </li>
32 </ul>
33 </div>
34 );
35 }
On line 2, we use the useLocation hook to obtain the location object.
The location object represents the current location URL and has the following
structure:
CHAPTER 4. CREATING A BLOG 131
1 {
2 pathname: '/alocation',
3 search: '?some=querytring',
4 hash: '#hashx',
5 state: null,
6 key: 'a key'
7 }
Note that it represents mainly all the information that a URL might have.
But what is most important from there right now is the state property.
There, is where we will have the data sent by the navigate function call
(second optional parameter). Knowing this now, if you look at the implementation
of the SearchResult.js component above, on line 16, you will see that we
can call to the location.state.map to render the JSON structure described
in section 4.1.1.
Having said that, let’s begin. As you might have guessed, I like to explain
concepts by showing running applications, and this is not an exception. To
explain the concepts of this chapter I have created an application called Task
List.
The figure 5.1 presents the architecture of the solution using a container1
diagram (C4 Model). As you can see, we have 7 containers. The Task List
UI, which is the React application, that consumes services from the two
microservices: User Auth and Task List. Note that the request from the
UI to the microservices goes through an API Gateway. The microservices
1
Not docker. A container in the C4 Model represents an application or a data store
139
CHAPTER 5. AUTHENTICATION AND AUTHORIZATION 140
use the Derby embedded (in memory) database. This will allows you (the
reader) to easily start them without installing or starting any additional
service.
Figures 5.2 and 5.3 shows the login screen and a task list main screen
respectively. In order to access to their task lists a user must type first
their username and password (in the login screen 5.2). That will generate a
request to the UserAuth microservice which validates user’s credentials, and
if successful it will return an access token. The access token allows the user
to access their tasks consuming the Task List services. Where to store in the
browser the access token is the topic of the next section, as this decision has
security implications.
Once authenticated, the user can retrieve their tasks. They are presented
in a list as shown in figure 5.3 with their expiration date. Depending how
close to the deadline they are, they will get a different background color. If
the user click on the checkbox, the task is marked as done (as shown in figure
5.3, second task). You can also delete tasks and add new tasks.
CHAPTER 5. AUTHENTICATION AND AUTHORIZATION 141
Untrusted data is the data that might come in an HTTP request: query
parameters, form input fields, headers and cookies. Suppose the following
back-end code that belongs to the fictitious myweb application:
1 value = request.param("q1");
2 response.add(value);
Note, from the back-end code, that there is no validation performed on the
value received from the untrusted source before that is sent into the response
to the browser. The browser will end up executing the malicious script.
However, you might wonder, why would someone create such a malicious
URL that will end up being executed on its own computer? The real problem
come with an attacker creating that malicious URL and somehow (probably
CHAPTER 5. AUTHENTICATION AND AUTHORIZATION 145
using social engineering) send that URL to a myweb authenticated user. For
instance, the victim might receive that URL hidden behind a link like "Click
to win!" in an HTML email page. If the authenticated user visit that link
the malicious script will be executed on their own computer. That malicious
script could be intended, among other things, to steal authentication tokens
or sessions IDs.
4 fetch("{URI}/tasks", {
5 method: "POST",
6 body: JSON.stringify({
7 date: inputForm.date,
8 text: inputForm.text,
9 }),
10 headers: {
11 "Content-type": "application/json; charset=UTF-8",
12 },
13 })
14 .then((r) => r.json())
15 .then((json) => setStateVariable(json.text));
16
17 //...
18
Note from the component above, that on line 4 we are making a request
to a remote API. As part of this request, on lines 6, 7, 8 and 9 using the
request’s body, we are sending untrusted data from two form inputs: date
and text (the form and state variables are not shown). Suppose now that the
server-side code that gets executed when calling to "{URI}/task", retrieve
the values from the request’s body, do some processing with them and as part
of the response data, it includes the text value, without applying any kind
CHAPTER 5. AUTHENTICATION AND AUTHORIZATION 146
However, there are many places in which you might want to render
untrusted data where React won’t do any escaping nor validation, like for
instance:
In the above case (as the value of the href attribute), if stateVariable
contains a script (malicious or not) it will get executed. There is a great
discussion thread on stackoverflow I recommend to read to have a better
understanding about XSS vulnerabilities in React.
https://bank.com/transfer?acc=my&amount=100
<a href="https://bank.com/transfer?acc=my&amount=100">Click to
,→ win!</a>
The victim might click the "Click to win!" link in a moment in which
she is authenticated in the bank application, transferring the money to the
attacker.
This vulnerability can be exploited also in POST request, using for instance,
a hidden form like below:
1 <body onload="document.forms[0].submit()">
2 ...
3
9 ...
10
11 </body>
Note the line 1 from the HTML form above, that means that as soon
as the browser render the page, the submission is done without requiring
any user action. CSRF attack requires the victim to visit the attacker web
site, and from there the malicious request is performed. The problem occurs
when the victim goes to the attacker web site when is authenticated in the
bank application, because the browser will do the submission including the
authentication cookies. The bank application cannot distinguish between a
valid request and a malicious request, and will perform the action.
1 <script>
2 function send() {
3 fetch("http://bank.com/transfer", {
4 method: "POST",
5 body: JSON.stringify({
6 acc: "my",
7 amount: 100,
8 }),
9 headers: {
10 "Content-type": "application/json; charset=UTF-8",
11 },
12 });
13 }
14 </script>
15 <body onload="send()">
16 //...
17 </body>
Additionally, with Ajax is possible to use others HTTP methods like PUT
or DELETE. Fortunately, this Ajax request is not executed by the browser
thanks to the same-origin policy restriction. Due to the malicious script runs
CHAPTER 5. AUTHENTICATION AND AUTHORIZATION 148
on the attacker’s web site (origin 1) and the request points to the victim’s
web site (origin 2), the browser detects this difference and the request is
cancelled. Two URLs have the same origin if the protocol, port (if specified),
and host are the same for both.
How does it work? If it is the server who needs to authorise the browser
to perform the request, how does the browser do the initial CORS request?
The browser deals with different types of request and depending on which
groups they belong to will work differently. There are three groups of request:
Simple Request, Non-Simple Request and Requests with Credentials
that we are detailing below:
Non-Simple Request Any request that doesn’t meet the simple request
conditions are considered non-simple ones. For the requests that belong
to this group, the browser send what is known as preflight request,
before performing the actual request. The goal of the preflight request
is to understand if the server can handle CORS and allows or not CORS
request. How does it work? Before sending a non-simple request, as
they are considered back-end services with side-effects, the browser
will send an OPTIONS request (called preflight request), including
the origin, the access-control-request-method (the HTTP method of
the actual request) and the access-control-request-headers (the HTTP
headers of the actual request). The server must decide if accepts or not
the request by including in the response the headers: access-control-
allow-origin (the origins allowed), access-control-allow-methods (the
methods allowed), access-control-allow-headers the headers allowed,
access-control-max-age (seconds to cache the OPTIONS response). If
CORS headers are not added in the response or the back-end service
does not allow the request, then, the actual request is not performed by
the browser. Otherwise, the browser will perform the actual request.
1 fetch('https://bank.com/transfer', {
2 credentials: 'include'
3 })
cookie Cookies are currently the best choice. In the last years they were
improved to accommodate them to new security vulnerabilities. Now,
cookies can be configured to be httpOnly (not accessible by JavaScript),
secure (only transmitted in secure channels) and same-site = strict
or lax (only sent by the browser in same-origin request). The full stack
application we have written as part of this chapter uses a cookie to
store the access token.
CORS not enabled Try at all cost to not enable CORS. If you require
to call APIs on different origins, use a back-end service to forward to
them. For instance, a reverse proxy or an API Gateway can be used
for this purpose.
Verify that only newer browsers are used Prepare a barrier on the back-
end that allows you to stop requests from certain older browsers. Browsers
are updated frequently and sometimes the fixes are related to security
flaws.
As we will see during this section, the implementation follows the best
practices described in section 5.2.4. Let’s start describing the authentication
CHAPTER 5. AUTHENTICATION AND AUTHORIZATION 152
flow illustrated in a sequence diagram in figure 5.5. Each rectangle from the
diagram represents a container2 from the (C4 Model). The first one, the
Task List UI represents the browser executing the React application. Note
that the React application talk to the API Gateway and not directly to
the microservices or web server in this case. We use that approach mainly to
be able to start the back-end services with any domain name and/or port.
The browser will always make request to the API Gateway and is this one
who forward the request to the appropriate back-end service. In this way, we
don’t have to enable CORS.
1 function App() {
2 const apiGwUrl = process.env.REACT_APP_API_GW;
3
4 return (
5 <Routes>
6 <Route
7 path={"/"}
8 element={
9 <>
10 <Menu apiGwUrl={apiGwUrl} />
11 <Welcome />
12 </>
13 }
14 />
15 <Route
16 path={"/tasklist"}
17 element={
18 <>
19 <Menu apiGwUrl={apiGwUrl} />
20 <PrivateRoute
21 component={<TasksList apiGwUrl={apiGwUrl} />}
22 requiredRoles={["SIMPLE", "ADMIN"]}
23 />
2
Not docker. A container in the C4 Model represents an application or a data store
CHAPTER 5. AUTHENTICATION AND AUTHORIZATION 153
24 </>
25 }
26 />
27 <Route path={"/login"} element={<Login apiGwUrl={apiGwUrl}
,→ />} />
28 </Routes>
29 );
30 }
31
From the component above we can see that we are using React Router
and when the route is the path / the rendered components are Menu.js and
Welcome.js. That will paint on the browser what is shown on figure 5.4.
After that, if the user clicks on the "Sign in" link it will request the
/login route, which ends up rendering the Login.js component (see line
27 on the App.js component above). Let’s discuss the Login.js component
presented below (the full source code of the component can be found here,
below is a fragment with the most relevant parts):
8 error: false,
9 });
10 const navigate = useNavigate();
11
12 function handleSubmit(e) {
13 e.preventDefault();
14
15 new User(props.apiGwUrl)
16 .login(loginForm.username, loginForm.password)
17 .then((v) => {
18 setErrorResponse({
19 msg: "",
20 error: false,
21 });
22
23 navigate("/");
24 })
25 .catch((v) => {
26 setErrorResponse(v);
27 });
28 }
29
30 return (
31 <div className="login">
32 <div className="login-logo">
33 <i className="bi bi-list-task" />
34 <b> Task List</b>
35 </div>
36 <Card>
37 <Card.Header className="login-msg">
38 Sign in to start your session
39 </Card.Header>
40 <Card.Body>
41 <Form onSubmit={handleSubmit}>
42 <InputGroup className="mb-2">
43 <InputGroup.Prepend>
44 <InputGroup.Text>
45 <i className="bi bi-person-circle"></i>
46 </InputGroup.Text>
47 </InputGroup.Prepend>
48 <Form.Control
49 name="username"
50 type="text"
CHAPTER 5. AUTHENTICATION AND AUTHORIZATION 155
51 placeholder="Username"
52 onChange={handleChange}
53 isInvalid={errorResponse.error}
54 />
55 <Form.Control.Feedback type="invalid">
56 {errorResponse.msg}
57 </Form.Control.Feedback>
58 </InputGroup>
59
60 <InputGroup className="mb-2">
61 <InputGroup.Prepend>
62 <InputGroup.Text>
63 <i className="bi bi-lock-fill"></i>
64 </InputGroup.Text>
65 </InputGroup.Prepend>
66 <Form.Control
67 name="password"
68 type="password"
69 onChange={handleChange}
70 placeholder="Password"
71 isInvalid={errorResponse.error}
72 />
73 </InputGroup>
74
There are two important parts to highlight. First is that the login
form is painted using React Bootstrap (Card, Form, InputGroup, etc, are
components that belong to React bootstrap). The login screen is presented
on figure 5.2. And second, when the form is submitted the handleSubmit
function on line 12 is invoked. The request flow that is triggered when the
login form is submitted is illustrated on figure 5.5 (green rectangle labelled
Authentication on the top left corner). Please, note from the figure 5.5, that
if the login is successful the response contains the user id, their name and
roles and most important, the httpOnly, secure and same-sate=strict
CHAPTER 5. AUTHENTICATION AND AUTHORIZATION 156
cookie. This cookie contains a paseto access token. Why paseto and not
jwt? Among other things that are very well described here, Paseto supports
the creation of encrypted tokens while jwt does not. If you want to see how
the cookie and the paseto tokens are created look at the source code of the
UserAuth microservice.
Note on line 15 above that the login request is delegated to the User
object. Let’s then study what this object does. See the source code below:
10 userId() {
11 return sessionStorage.getItem(STOREUID);
12 }
13
14 userName() {
15 return sessionStorage.getItem(STOREUNAME);
16 }
17
18 hasRole(role) {
19 let userRoles = sessionStorage.getItem(STOREUROLES);
20 return role.includes(userRoles);
21 }
22
23 static current(apiUrl) {
24 return new User(apiUrl);
25 }
26
27 logout() {
28 return fetch(this.apiUrl + "/auth/logout", {
29 method: "POST",
30 headers: {
31 "Content-type": "application/json; charset=UTF-8",
32 },
33 })
34 .then((response) => response.json())
CHAPTER 5. AUTHENTICATION AND AUTHORIZATION 157
35 .then((json) => {
36 sessionStorage.clear();
37 return Promise.resolve();
38 });
39 }
40
41 login(userName, password) {
42 return fetch(this.apiUrl + "/auth/login", {
43 method: "POST",
44 body: JSON.stringify({
45 user: userName,
46 pass: password,
47 }),
48 headers: {
49 "Content-type": "application/json; charset=UTF-8",
50 },
51 })
52 .then((response) => {
53 if (response.status === 401) {
54 return Promise.reject({
55 msg: "Username or password incorrect...",
56 error: true,
57 });
58 }
59 return response.json();
60 })
61 .then((json) => {
62 if (json.result === "success") {
63 sessionStorage.setItem(STOREUNAME, json.user.name);
64 sessionStorage.setItem(STOREUROLES, json.user.roles);
65 sessionStorage.setItem(STOREUID, json.user.id);
66 return Promise.resolve();
67 } else {
68 return Promise.reject({
69 msg: json.message,
70 error: true,
71 });
72 }
73 });
74 }
75 }
On line 41 above, you can see the implementation of the login method.
The method performs a POST request passing the username and password
CHAPTER 5. AUTHENTICATION AND AUTHORIZATION 158
5 function handleLogout(e) {
6 e.preventDefault();
7 User.current(props.apiGwUrl)
8 .logout()
9 .then(() => navigate("/login"));
10 }
11
12 return (
13 <Navbar bg="light" expand="sm">
14 <Navbar.Brand href="#">
15 <Link to="/">Understanding React</Link>
16 </Navbar.Brand>
17 <Navbar.Toggle aria-controls="basic-navbar-nav" />
18 <Navbar.Collapse id="basic-navbar-nav">
19 <Nav className="mr-auto">
20 <Nav.Link href="#">
21 <PrivateRoute
22 component={<Link to="/tasklist">Task List</Link>}
23 requiredRoles={["SIMPLE", "ADMIN"]}
24 ></PrivateRoute>
25 </Nav.Link>
26 </Nav>
27 <Nav>
28 {!userName && <Link to="/login">Sign in</Link>}
29 {userName && (
30 <a href="#task" onClick={handleLogout}>
CHAPTER 5. AUTHENTICATION AND AUTHORIZATION 160
7 return component;
8 }
Since now the user is authenticated, user id, names and roles are stored
in the session storage accessible by the User object. So, the Link component
passed as prop to the PrivateRoute component on line 22 above is rendered.
You can can see now on figure 5.6 the link highlighted in a red box.
Now, if the user clicks on the Task List link, the TaskList.js component
will be rendered. On mounting, a simple GET request is performed by the
component to retrieve user’s task (/tasks API from the TaskList microservice),
but this time, since the user is authenticated and the request goes to the same
origin, the cookie is included by the browser (nothing additional is required
by the developer). The flow is described in figure 5.7. At server-side (see
source code here) the token is obtained from the cookie (if there is no cookie,
the request is rejected as 401 unauthorized) and verified, if it was changed it
will be discarded as invalid. After that, the roles that the token contains are
compared against the roles required to execute the /tasks API. If everything
success the user’s task are returned, and the React application shows them
as you can see on figure 5.3.
CHAPTER 5. AUTHENTICATION AND AUTHORIZATION 161
5.4 Logout
To do a proper logout, two things must happen: session storage must be
deleted, and then and more importantly, the cookie must be deleted. The
deletion of the cookie must be done server-side, and for that the UserAuth
microservice exposes the /logout API. You can see above, the handleLogout
function on the Menu.js component on line 5, that calls the User.logout()
method which performs a POST request to the /logout API. On success, it
just clear the session storage (line 36 on User object) and returns a resolved
Promise. Finally, the login screen is shown. The logout flow is shown on
figure 5.8.
[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript
[2] https://reactjs.org/docs/getting-started.html
[3] https://www.ecma-international.org/
[4] https://nodejs.org/
[5] http://latentflip.com/loupe/
[6] https://material-ui.com/
[7] https://html5up.net/
163