TG
Development·19 min read

Introduction to React

How to set up webpack, babel and the css/image loaders, plus the main React lifecycle methods

Ler em português
Introduction to React

Introduction to React

Class 1 - React Concepts

Let's go over the main concepts of React!

What is React?

  • It's a library for building user interfaces;
  • It was built with Javascript;
  • It can be used for virtual reality, mobile, and web interfaces — basically any user interface that runs on Javascript;
  • Used to build SPAs (Single Page Applications), a concept from 2011 that came along with Angular. With SPAs, the backend returns JSON and the frontend handles routing and consumes the JSON. It's a single page — the browser doesn't reload, there's no refresh.
  • Can we call it a framework?
    • React has become an ecosystem — mobile, web, desktop — so in that sense, yes, it's a framework.
  • Everything lives inside Javascript: CSS and images all live inside Javascript.
  • React / ReactJS / React Native
    • React = the UI-building library, used both on the web (with React) and on mobile (with React Native)
    • ReactJS = React's behavior in the browser, integrating with React DOM
    • React Native = the combination of React with native UI building for Android and iOS.

Hello React:

import React from 'react';

import './button.css';
import icon from './button.png';

function Button() {
 return (
	 <button>
		 <img src={icon} />
	 </button>
 );
}

That's code written with React.

You always have to import the React lib into your page's components.

In this React code we have Javascript, CSS and an image.

JS: the function CSS: the button.css file Image: button.png

The one that reads all of this is webpack, which manages to embed it into a native Javascript bundle. Babel translates the more modern code into a version the browser understands.

The code doesn't get less performant because webpack + babel handle the optimization.

This code is actually a .JSX React file with Javascript. Even the HTML elements in the file are actually React elements.

Advantages

  • Code organization
    • Componentization: everything is a component, and other frameworks (Angular, Vue) copied this same solution. Small chunks of code get reused; we split into components when we split the logic. A component can be understood as logic (JS), styling (CSS) and structure (HTML) bundled together — they can be reused or simply removed and the page still works.
    • Separation of concerns:
      • Back-end: business rules
      • Front-end: interface
  • One API and many clients:
    • You can have one backend exposing a REST API, with a web project and a mobile project consuming the same backend.
  • Declarative programming
    • Imperative programming: the developer describes every step the computer should take.
    • Declarative programming: you state the result you expect, and the system behaves according to the state you pass in.

JSX

  • Writing HTML inside Javascript;
  • With React we can create our own elements;

Before JSX:

// BEFORE
function Button() {
 return React.createElement(
	 'button',
	 { type: button },
	 React.createElement(
		 'span',
	 { class: 'icon' }
	 )
    )
}
<button type="button">
 <span className="icon"/><span>
</button>

Very ugly, very verbose…

And now with JSX:

// With JSX
function Button() {
 return (
	 <button type="button">
		<span className="icon"></span>
	 </button>
 );
}

Now I can create a Header function and return a Button that contains the whole structure of a button. And the Button can be reused wherever we want, just like the Header.

// Our own elements
// (components)

function Header() {
 return <Button />

Both Header and Button are components.

Imperative vs Declarative Programming

In imperative programming you give the steps and the conditions for something to happen. In declarative programming you give only the conditions for something to happen.

IMPERATIVE

const notificacoes = 0;

function montaBadge(num) {
 if (notificacoes === 0 !& num > 0) {
 // Add badge
 // container.appendChild(badge)..
 }

 if (notificacoes !== 0 !& num > 0) {
 // Just change the number
 // badge.innerHTML = num...
 }

 if (notificacoes !== 0 !& num === 0) {
 // Remove badge
 // container.removeChild(badge)
 }
}

DECLARATIVE

!/ We don't compare to the previous state
function Badge({ num }) {
 return (
	 <div id="container">
	   { num > 0 !& <div id="badge">{num}</div>}
	   <span className="icon"></span>
	 </div>
 );
}

Babel / Webpack

  • The browser doesn't understand React code with images and CSS;
  • Babel converts the JS code into something the browser understands;
  • Webpack has several jobs:
    • It creates the bundle — a single file with the entire application code;
    • It teaches Javascript how to import CSS files, images, etc. through loaders;
    • Live reload with webpack dev server: every time you change code the browser refreshes with the new bundle.

Class 2 - Setting up the structure

Create a folder called intro-react in your workspace, and run yarn init -y to create a package.json at the project root.

Create a src folder which will hold the frontend Javascript code.

Create an index.js file at the root of src — that will be the entry point of the frontend application.

Installing webpack, babel, react and react-dom

Then install the libraries as dev dependencies:

yarn add @babel/core @babel/preset-env @babel/preset-react webpack webpack-cli -D

These libs handle the integration between webpack, babeljs and reactjs.

Install the libraries:

yarn add react react-dom

Configuring Babel

Then create a file at the project root: babel-config.js, to set up babel.

module.exports = {
  presets: ["@babel/preset-env", "@babel/preset-react"]
};

@babel/preset-env = converts functionality the browser doesn't understand into a version it does — for example import/export, arrow functions, classes, and other modern Javascript features the browser doesn't yet support. This lib downlevels to the older JS ES5.

@babel/preset-react = converts React features the browser doesn't understand — for example JSX is turned into plain JS.

Configuring Webpack

Create a file at the project root: webpack.config.js.

entry: the application's entry file.

path.resolve(__dirname, "src", "index.js"): this nodejs property resolves the slash differences when navigating between directories — Windows handles it one way, Linux another. The resolve function takes care of that for us.

output: the location where webpack will emit the transpiled code, the one that goes to production and that the browser understands. It takes the path (the file's location) and the filename.

We can create a public folder at the project root to receive the bundle.

 output: {
    path: path.resolve(__dirname, "public"),
    filename:  'bundle.js'
  }

Next we configure the webpack module, which holds the rules:

  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  }

We declared one rule for now: the test says the file has to be .js (i.e. a Javascript file in our application), excluding any Javascript inside node_modules since it's already transpiled (that's the library author's responsibility). We declared that we'll use a loader called babel-loader — babel handles Javascript files, there are other loaders to handle images, CSS, etc. For now we'll only use this one. To make it work we install it as a dev dependency:

yarn add babel-loader -D

And now to test the configuration we add a script to package.json to build the application. Build is the act of webpack transpiling our project and putting everything into bundle.js in the public folder as configured in webpack.config.js.

"scripts": {
    "build": "webpack --mode development"
  },

Now just run:

yarn build

And the bundle.js file will be generated. Notice that at the end we get the same code written in an older Javascript dialect the browser supports:

var soma = function soma(a, b) {
  return a + b;
};
alert(soma(1, 3));

Don't worry about the previous code — that's what makes import/export work in the browser. Thanks webpack, babel!

Now let's test the bundle.js in the browser.

Create an index.html file in the public folder.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>React JS</title>
</head>

<body>
    <h1>Hello World</h1>

    <script src="./bundle.js"></script>
</body>

</html>

And opening the address in the browser:

/Users/YOUR_USER_HERE/Developer/workspace/intro-react/public/index.html

We get an alert with the sum result and a giant h1 with "Hello World".

Live Reload

To get live reload working we need a dev library and some configuration:

yarn add webpack-dev-server -D

And in webpack.config.js we add:

devServer: {
	contentBase: path.resolve(__dirname, "public")
},

And in package.json we add one more script:

"scripts": {
   "build":  "webpack --mode development",
   "dev": "webpack-dev-server --mode development"
  },

and we run:

yarn dev

Now we can go to the browser and type in the address bar:

[http://localhost:8080/](http://localhost:8080/)

And we'll have the project running — and if we change the code, it updates and re-renders on the screen. Just change the Javascript again.

A detail:

"scripts": {
   "build":  "webpack --mode development",
   "dev": "webpack-dev-server --mode development"
  },

That --mode development generates a bundle that's still readable. If we change that property to --mode production it generates a bundle that's impossible to read — single-line, minified, in a way the computer processes faster, optimizing performance.

"scripts": {
   "build":  "webpack --mode production",
   "dev": "webpack-dev-server --mode development"
  },

Result:

!function(e){var t={};function  r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof  Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return  Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t){alert(11+3)}]);

When the code goes to production we'll ship the minified bundle by running yarn build.

End: https://github.com/tgmarinho/intro-react/tree/aula02-configurando-estrutura

Class 03 - Creating the Root Component

Let's create a Root component that is the parent of every component inside the React application.

We can use React because we configured preset-react in babel config, which converts JSX (React) into JS the browser understands.

In public/index.html we replace the <h1> element with a div that will host the root component:

<div  id="app"></div>

We create an App.js component:

import React from "react";

function App(params) {
  return <h1>Hello Thiago Marinho</h1>;
}

export default App;

And in index.js we use React DOM's render method to render the App component inside the div with the id "app". With that, all the HTML, Javascript, CSS and images contained in the JSX will appear starting from that div:

import React from "react";
import { render } from "react-dom";

import App from "./App";

render(<App />, document.getElementById("app"));

End: https://github.com/tgmarinho/intro-react/tree/aula03-criando-componente-raiz

Class 4 - Importing CSS

To import CSS from inside JSX we need to add two new loaders to webpack.

yarn add style-loader css-loader -D

We'll add one more rule in webpack.config.js:

{
  test: /\.css$/,
  use: [{ loader: "style-loader" }, { loader: "css-loader" }]
}
  • Style Loader: lets us import CSS files. It takes the CSS file — for example App.css — and injects its content inside a <style> tag in the HTML.

  • CSS Loader: handles other files referenced by the CSS, for example a background: url('../image/nice.jpg') — it picks up the resources declared inside the CSS.

Now we create a CSS file in the project: src/App.css:

body {
  background: #7159c1;
  color: #fff;
  font-family: Arial, Helvetica, sans-serif;
}

And finally we import it in App.js:

import React from "react";
import "./App.css";

function App(params) {
  return <h1>Hello Thiago Marinho</h1>;
}

export default App;

Now just run the project: yarn dev and check the result — you should see a purple screen with white text.

End: https://github.com/tgmarinho/intro-react/tree/aula04-importando-css

Class 05 - Importing Images

To import images from inside JSX, we need to add one more loader to webpack.

yarn add file-loader -D

And we add a new rule to load images with the extensions .gif, .png, .jpeg or .jpg, upper or lowercase.

{
  test: /.*\.(gif|png|jpe?g)$/i,
  use: {
     loader: "file-loader"
       }
}

And finally we import the image and put it in the img tag:

import React from "react";
import "./App.css";

import homeoffice from "./assets/images/homeoffice.png";

function App() {
  return <img src={homeoffice} />;
}

export default App;


Done — so far we're already importing CSS and images! =)

End: [https://github.com/tgmarinho/intro-react/tree/aula05-importando-imagens](https://github.com/tgmarinho/intro-react/tree/aula04-importando-imagens)


## Class 06 - Class Components

With React we can write components using classes — useful for defining state and adding lifecycle methods that we'll see later.

We create a `src/components` folder and a `TechList.js` file inside it:

import React, { Component } from "react";

class TechList extends Component { state = { techs: ["Node.JS", "ReactJS", "React Native"] };

render() { return (

  • Node.js
  • ReactJS
  • React Native
); } }

export default TechList;


And use the new component in `App.js`:

import React from "react"; import "./App.css";

import TechList from "./components/TechList";

function App() { return ; }

export default App;

When you run `yarn dev` to start the project and open the browser, you'll see an error in the console asking you to add a babel plugin. That's because babel doesn't support this new syntax of adding `state` inside the class without defining a `constructor`.

We need to add a babel plugin to enable the simpler React class component syntax.

yarn add @babel/plugin-proposal-class-properties -D


And I add it to `babel.config.js`:

module.exports = { presets: ["@babel/preset-env", "@babel/preset-react"], plugins: ["@babel/plugin-proposal-class-properties"] };


Now running the project with `yarn dev` it should work perfectly.

End: [https://github.com/tgmarinho/intro-react/tree/aula06-class-components](https://github.com/tgmarinho/intro-react/tree/aula06-class-components)


## Class 07 - State and Immutability

Now we're going to manipulate the state variable we declared last class — `techs`, which holds an array of new technologies.

We can iterate over the array and render it on screen:

... render() { return (

    {this.state.techs.map(tech => (
  • {tech}
  • ))}
); } ...


Every time we map or iterate a list, we need to pass a `key` prop on each item to silence the warning. That `key` must be unique — generally an ID.

Every time the application state changes, the `render` method runs again.

To update the state, we use the `setState` method:

handleInputChange = e => { this.setState({ newTech: e.target.value }); };

And in the text input we add:

<input type="text"value={this.state.newTech} onChange={this.handleInputChange} />

On each input change, `handleInputChange` runs, which calls `setState` updating `newTech`. That state change re-runs `render(){..}`.

render() { return ( <>

{this.state.newTech}

    {this.state.techs.map(tech => (
  • {tech}
  • ))}
</> ); }


Note: I used the `<>` and `</>` tags, which mean a Fragment — a code fragment. Since we added a new `input` tag at the same level as the `ul` and the `h1`, the components need a parent — they can't be "floating". That's why we use a Fragment. It could be a div or another container element, but the advantage of a Fragment is that it doesn't add a visual element to the screen, which would otherwise interfere with the project's styling and HTML maintenance.

Now we need to push the text in `newTech` into the `techs` array.

All state in React is immutable: to add a new item to `techs` we have to recreate the array, copying the current state and adding the new item. Removing works the same way.

import React, { Component } from "react";

class TechList extends Component { state = { newTech: "", techs: ["Node.JS", "ReactJS", "React Native"] };

handleInputChange = e => { this.setState({ newTech: e.target.value }); };

handleSubmit = e => { e.preventDefault(); this.setState({ techs: [...this.state.techs, this.state.newTech], newTech: "" }); };

render() { return (

{this.state.newTech}

    {this.state.techs.map(tech => (
  • {tech}
  • ))}
); } }

export default TechList;


*React state is immutable — it doesn't mutate, it's recreated.*



End: [https://github.com/tgmarinho/intro-react/tree/aula07-estado-e-imutabilidade](https://github.com/tgmarinho/intro-react/tree/aula07-estado-e-imutabilidade)



## Class 08 - Removing items from state

To remove items from state we need to recreate the state — returning a new array without the element to be deleted.

handleDelete = tech => { this.setState({ techs: this.state.techs.filter(t => t !== tech) }); };


We receive the element to be deleted as a parameter and iterate over all items filtering out the ones that match — `filter` recreates a new array with only the items that don't match the given id.

import React, { Component } from "react";

class TechList extends Component { state = { newTech: "", techs: ["Node.JS", "ReactJS", "React Native"] };

handleInputChange = e => { this.setState({ newTech: e.target.value }); };

handleSubmit = e => { e.preventDefault(); this.setState({ techs: [...this.state.techs, this.state.newTech], newTech: "" }); };

handleDelete = tech => { this.setState({ techs: this.state.techs.filter(t => t !== tech) }); };

render() { return (

{this.state.newTech}

    {this.state.techs.map(tech => (
  • {tech} <button onClick={() => this.handleDelete(tech)} type="button"> Remove
  • ))}
); } }

export default TechList;

*The immutability concept is precisely about not assigning a value directly to a state property, but recreating a new value for state based on the current one.*

End: [https://github.com/tgmarinho/intro-react/tree/aula08-removendo-itens-do-estado](https://github.com/tgmarinho/intro-react/tree/aula08-removendo-itens-do-estado)

## Class 09 - React Props

Let's go through the most important concept in React: props. Props (or properties) are everything we pass into a component.

It's worth saying that a React component is a function, and that function may or may not receive parameters — and in a component these parameters are the props.

We create a new component: `src/components/TechItem`:

import React from "react";

function TechItem({ tech, onDelete }) { return (

  • {tech}
  • ); }

    export default TechItem;

    
    And use the new component in `TechList`:
    
    

    import React, { Component } from "react";

    import TechItem from "./TechItem";

    class TechList extends Component { state = { newTech: "", techs: ["Node.JS", "ReactJS", "React Native"] };

    handleInputChange = e => { this.setState({ newTech: e.target.value }); };

    handleSubmit = e => { e.preventDefault(); this.setState({ techs: [...this.state.techs, this.state.newTech], newTech: "" }); };

    handleDelete = tech => { this.setState({ techs: this.state.techs.filter(t => t !== tech) }); };

    render() { return (

    {this.state.newTech}

      {this.state.techs.map(tech => ( <TechItem key={tech} tech={tech} onDelete={() => this.handleDelete(tech)} /> ))}
    ); } }

    export default TechList;

    
    A few notes: the item-deletion method must stay in the class that owns the state, and we can pass the function by reference to `TechItem` so it just calls it.
    
    The `key` always goes on the parent component — the root of the iteration.
    
    
    End: [https://github.com/tgmarinho/intro-react/tree/aula09-propriedades-do-react](https://github.com/tgmarinho/intro-react/tree/aula09-propriedades-do-react)
    
    
    ## Class 10 - Default Props & PropTypes
    
    Default props and prop-types help the developer avoid passing invalid types to props, or forgetting to set a non-required default for a component or its element.
    
    * Default Props:
    Declared inside classes:
    

    static deaultProps = { newTech: "Type the tech here..." };

    
    Declared inside functions:
    

    import React from "react";

    function TechItem({ tech, onDelete }) { return (

  • {tech}
  • ); }

    TechItem.defaultProps = { tech: "Hidden" };

    export default TechItem;

    
    
    * PropTypes:
    
    	```
    	yarn add prop-types
    	```
    
    Now add it to the code:
    
    

    import React from "react"; import PropTypes from "prop-types";

    function TechItem({ tech, onDelete }) { return (

  • {tech}
  • ); }

    TechItem.defaultProps = { tech: "Hidden" };

    TechItem.propTypes = { tech: PropTypes.string, onDelete: PropTypes.func.isRequired };

    export default TechItem;

    
    When a prop is required I pass `isRequired`; when it isn't, I omit `isRequired` and declare it in `defaultProps`. The browser will always log a warning if a rule was broken, and we can fix it in the code.
    
    End: [https://github.com/tgmarinho/intro-react/tree/aula10-default-props-e-prop-types](https://github.com/tgmarinho/intro-react/tree/aula10-default-props-e-prop-types)
    
    ## Class 11 - Component Lifecycle
    
    React has several lifecycle methods.
    
    Let's detail the three main ones, the most used:
    
    

    componentDidMount() { }

    Runs as soon as the component appears on screen. It's recommended for fetching resources from an external API to populate state and display on screen.
    
    

    componentDidUpdate(prevProps, prevState) { // this.props, this.state }

    
    Runs whenever props or state change — we can access the previous props and state.
    
    
    

    componentWillUnmount() { }

    
    Runs when the component is removed.
    
    
    Let's see it in practice — we need to save the tech array to `localStorage` every time the user adds or removes a tech. The method that runs on every state change is `componentDidUpdate` (the component made a change).
    
    

    // Runs whenever props or state change componentDidUpdate(_, prevState) { if (prevState.techs !== this.state.techs) { localStorage.setItem("@techs", JSON.stringify(this.state.techs)); } }

    
    So I check whether the previous array differs from the current one — if so, I do something. Here I'm saving a new tech array to `localStorage` based on the current array.
    Since I don't need `prevProps` (this component doesn't receive props), I ignore it with an `_` in the first parameter.
    
    Now another scenario: we need the `techs` array to come pre-populated if there's something saved in `localStorage` when the component mounts.
    
    

    // Runs as soon as the component appears on screen componentDidMount() { const techs = localStorage.getItem("@techs");

    if (techs) {
      this.setState({ techs: JSON.parse(techs) });
    }
    

    }

    
    Now I grab `techs` from `localStorage`, check whether something actually came back, and if so, save it into the `techs` state. If you reload the page, you'll see the tech array comes back with the same content stored in `localStorage`.
    
    
    The `componentWillUnmount` function is rarely used, but one scenario would be clearing an event listener. Imagine you're using a `setTimeout` inside `componentDidMount` and when the component leaves the screen the `setTimeout` keeps running. The right thing to do is use `componentWillUnmount` to clear the event listener — in this case, `clear` the `setTimeout`.
    
    
    The most used methods are `componentDidMount`, then `componentDidUpdate` and finally `componentWillUnmount` — usually in that order.
    
    End: [https://github.com/tgmarinho/intro-react/tree/aula11-ciclo-de-vida-do-componente](https://github.com/tgmarinho/intro-react/tree/aula11-ciclo-de-vida-do-componente)
    
    ## Class 11 - Debugging React with DevTools
    
    To debug a React application, it's very useful to use the Google extension: [react-developer-tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi)
    
    After installing, just right-click → Inspect Element and select the React tab.
    
    It will show all components, with their state and props.
    
    It's great for watching React's lifecycle in action.
    
    That's the end of the introduction to React.
    
    Now there's a [challenge](https://github.com/tgmarinho/faceseat) for you to tackle. Let's code!
    

    Thiago Marinho

    September 23, 2019 · Brazil