React is a Library. What is a library you might ask?
Its a Collection of code that solves a particular problem.
In their own words:
React is a JavaScript library for building user interfaces.
So where is this code? Can I see it?
Well in react's case, yes. Because it is open source, the code lives in Github a very popular place for open source Projects. Examples of open source software are: Linux, VLC Media Player, Mozilla Firefox, among others...
Is all software open source? Short answer is no. A lot of software is closed source, that means the source code is owned by a Company, and therefore not available to the general public, examples of this are: Microsoft windows, Google Chrome, Microsoft Office, among others...
Other relevant libraries for frontend devleopment are :
- moment.js — helps you manipulate dates with ease
- D3.js — helps you transform data into graphics and visualizations.
One common thing between libraries is that they all possess an API, this is the way you use these libraries.
For example, for React you can see how to use it on their docs page https://reactjs.org/docs/getting-started.html
There is no point in using a tool if there is no added value.
So what value does react bring into the frontend game?
Why don't we just manipulate the DOM directly with pure javascript or JQuery?
Since an image is worth a 1000 words, I will present you with a problem and two different solutions.
The old-style aproach, and the new-style react appoach.
Let's dig into it:
Understand REST APIs and HTTP protocol
For simplicity, a REST API is a service hosted on a web server, which adheres to some rules, which provides data, usually as JSON
To interact with it, normal HTTP protocol is used, the same way you access an url to request let's say google.com
.
HTTP is a very important protocol which we use all over the internet, having a good grasp on its details is crucial,
This are some great resources to learn about it:
-
HTTP Overview: https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview
-
HTTP Headers: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
-
HTTP status codes: https://httpstatuses.com/
Create a table with user data from a REST API.
We will use the data provided by: https://jsonplaceholder.typicode.com/users
Go ahead and open it on the browser, you can see the raw JSON data.
A single user as the following form
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
}
Not all them are relevant, let's just use — id, name, username, email, phone
for our use case.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!--
Our table starting point
an id will helps identify it
on our js code
-->
<table id="usersTable">
<!--
table rows and columns will be created dynamically
after we fetch the data from the network
-->
</table>
<script>
// JS Magic goes here
</script>
</body>
</html>
From here we will focus our atention inside the script tags
<!-- index.html -->
<!-- .... -->
<script>
/**
* returns - a promise with json in the form of an array of
* users with the properties seen before
*/
async function getUsers() {
// retrieve data using the fetch api
// more info on it: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
// the use case is simple we just need to pass the url as parameter
// response will be of type https://developer.mozilla.org/en-US/docs/Web/API/Response
const response = await fetch("https://jsonplaceholder.typicode.com/users");
// reference: https://developer.mozilla.org/en-US/docs/Web/API/Body/json
const json = response.json();
}
</script>
<!-- ..... -->
Like i said before, we just care about a subset of fields for our users:
id, name, username, email, phone
Let's create a preProcessUsers
function in charge of stripping down, not needed fields.
To do that we will iterate over the users and return our new array, which should look like this:
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"phone": "1-770-736-8031 x56442",
},
{
"id": 2,
"name": "Charlie Chaplin",
"username": "Happychap",
"email": "say_no_to_kaiser_moustache@thegreatdictator.moc",
"phone": "1-2311-23123-34",
},
// and so on...
]
Lets write the function preProcessUsers
<!-- index.html -->
<!-- .... -->
<script>
// ...
function preProcessUsers(usersArray) {
// Array.map will loop through the array fields and create a new array
// based on our logic
// more info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
// an arrow function could also be used
// usersArray.map(user => {..fn body..});
const newUsersArray = usersArray.map(function(user) {
// here we return only the fields we care about for each user
return {
id: user.id,
name: user.name,
username: user.username,
email: user.email
}
});
// now we can return our new clean array
return newUsersArray;
}
</script>
<!-- ..... -->
Table Reference: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table
Everybody knows a table has rows and columns.
A table has an header which tells you what fields you are looking at, this is represented by <th>
Our fields id, email
, etc, are the columns, <td>
elements
Each user will be a table row <tr>
Element
<!-- index.html -->
<!-- .... -->
<script>
// ...
function buildTableHead(user) {
/*
Acording to spec all <th> elements should be inside a <tr>
So by definition they are children of <tr>
## Tree representation of the DOM nodes for the header ##
<thead> —> Parent Node or Root inside our context
|
<tr> —> Children node of <thead>
/ \
<th> <th> —> Children nodes of <tr>, also called leaf nodes, just like the leafs on a real tree
## Translates to ##
<thead>
<tr>
<th>
field 1
</th>
<th>
field 2
</th>
...
th>
field N
</th>
</tr>
</thead>
*/
// Counter intuitively we will create this tree in the leafs -> root direction
// a bottom-up aproach considering the previous graph
// 1. leaf nodes <th>
// a list of <th> elements
const thElements = document.createDocumentFragment();
Object.keys(user).forEach(key => {
const thElm = document.createElement('th');
thElm.textContent = key;
thElements.appendChild(thElm);
})
// 2. node <tr>
const trElement = document.createElement("tr");
// 3. node <thead>
const tableHeadElement = document.createElement("thead");
// 4. Assemble the pieces
trElement.appendChild(thElements);
tableHeadElement.appendChild(trElement);
return tableHeadElement;
}
function buildTableBody(users) {
const tableBodyElement = document.createElement("tbody");
const trElements = document.createDocumentFragment();
users.forEach(user => {
const trElement = buildSingleRow(user);
trElements.appendChild(trElement);
});
tableBodyElement.appendChild(trElements);
return tableBodyElement;
}
function buildColumns(user) {
// aka Column nodes
const tdElements = document.createDocumentFragment();
// lets loop over user properties and create the columns
// value will be the id, name, and so on, e.g. ['1', 'Charlie Chaplin',...]
Object.values(user).forEach(value =>{
const tdElm = document.createElement("td");
tdElm.textContent = value;
tdElements.appendChild(tdElm);
});
return tdElements;
}
function buildSingleRow(user) {
const trElement = document.createElement("tr");
const tdElements = buildColumns(user);
trElement.appendChild(tdElements);
return trElement;
}
function buildAllRows
// Here we assemble our table
function renderTable(tableID, usersData) {
const tableRoot = document.getElementById(tableID);
// Assemble the pieces
const tableBodyElement = buildTablebody(usersData);
const tableHeadElement = buildTableHead(usersData);
tableRoot.appendChild(tableHeadElement);
tableRoot.appendChild(tableBodyElement);
}
// when page is loaded, meaning all the html, css and so on we start our logic
window.onload = async function() {
// 1. fetch our data, this is async returns a promise, so we use await keyword
const usersRawData = await getUsers();
// 2. preprocess the data
const processedUsersData = preProcessUsers(usersRawData);
// 3. uff, render the table 😓
renderTable(processedUsersData);
};
// TODO: Add references for the dom apis used here
</script>
<!-- .... -->
I don't know about you, but my reason tells me, this is way too much code just to load a simple html table...
What if we need dynamic actions to add
, edit
and remove
users?
What if I tell you that 90% of the time you won't need to manipulate the DOM tree?
Yes that's right, no more document.something
I can feel you eyes sparkling already, thinking you have just found the lost treasure of El dorado.
From now on, you will worry about your nice, tidy Components
. How to represent your data and how to manipulate it and react will gladly and obidiently manipulate the DOM for you...
Let us once again, create an index.html
, only this time we will include the react scripts
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="reactRoot">
<!-- This is where react will render our JSX components -->
</div>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<!--
babel will take care of the JSX part, it acts as a preprocessor
this tool gives us a visualization of how JSX look after parsed by babel: https://rajasegar.github.io/ast-builder/
reference: https://reactjs.org/docs/add-react-to-a-website.html#add-jsx-to-a-project
-->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
// Freshly juiced react code goes here
</script>
</body>
</html>
<!-- index.html -->
<!-- .... -->
<script type="text/babel">
class Table extends React.Component {
constructor(props) {
super(props);
// we need to store our users, lets use react state
// right now its empty, but when the the components mounts in the
// dom ComponentDidMount is called by react
// making the call to the api and getting the data
this.state = {users: []};
}
// lifecycle method in invoked when component mounts
// great to fetch data
async ComponentDidMount() {
const usersRawData = await this.getUsers();
const processedUsersData = this.preProcessUsers(usersRawData);
this.setState({users: processedUsersData});
}
// ipsis verbis from previous implemntation
preProcessUsers(usersArray) {
const newUsersArray = usersArray.map(function(user) {
// here we return only the fields we care about for each user
return {
id: user.id,
name: user.name,
username: user.username,
email: user.email
}
});
return newUsersArray;
}
async getUsers() {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
return response.json();
}
renderCols(user, type, element) {
return Object[type](user).map(colVal => React.createElement(element, null, colVal));
}
render() {
return (<table>
<thead>
<tr>
{this.renderCols(this.state.users[0], "keys", "th")}
</tr>
</thead>
<tbody>
{this.state.users.map(user => <tr>{this.renderCols(user, "values", "td")}</tr>)}
</tbody>
</table>)
}
}
// renders Table Component on the designated DOM element
ReactDOM.render(
<Table/>,
document.getElementById('reactRoot')
);
</script>
<!-- .... -->
TODO:
- break down the react example and add comments
- create pages with working example add links