React App from Scratch

This blog shows how to setup a React application.

  • with express
  • with react router
  • with/without react-scripts

Preconditions

  • OS with bash-like terminal
  • nodejs is installed (by that you can call npm -version in your bash)

Setup React Project

Create React App is the official way how to create React applications. It comprises all common used features and is updated on a regular basis. To create a project called react-app : Run in the parent directory the commands:

npx create-react-app react-app
cd react-app
npm start

which creates all content in a new created sub directory called react-app and starts a test server. A glance into the generated package.json:

{
  "name": "react-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-scripts": "0.9.5"
  },
  "devDependencies": {},
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

reveals that react-scripts is used to do the magic. Cf. the section Without react-scripts below if you do not want to use it. React applications are single page applications. That means that you have one and only one HTML page, in our case react-app/public/index.html:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="shortcut icon" href="/favicon.ico">
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

A bundler used in the background will bundle all React files that we write into one huge /static/js/bundle.js, which will be added to the above index.html by adding the HTML tag <script type="text/javascript" src="/static/js/bundle.js"></script>. The index.html with its assests and the generated bundle.js needs then to be put onto a server that browser running on clients can access them. Run in react-app folder

npm run build

This creates a build directory which holds the generated index.html, bundle.js etc. that we can put e.g. on an express server.

Express

Even though react-scripts provides a way to test our React application, you will not use this approach in production. We assume that our back-end will be a nodejs server that should provide the index.html with its assests.

Run in react-app folder

npm install express path --save

express has been added as dependency to the project.

In the react-app folder parallel to src folder create file app.js:

const express = require('express');
const path = require('path');

const app = express();

app.use(express.static(path.join(__dirname, 'build')));

app.get('*', (req, res) => {
    res.sendFile(path.join(__dirname, '/build/index.html'));
});

app.listen(4711);
console.log('Open http://localhost:4711/ in Browser to start');

To test the server run

npm install && node app.js

Open in browser http://localhost:4711/ you should see the page again.

Write React code

There is an index.js in the src:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

This index.js contains the root code that uses react-dom to render the content of our root App component inside the <div id="root"></div> of the index.html.

Let’s modify the App that it uses another component to which we pass a property to see how this works.

In src create a HelloWorldText.js:

import PropTypes from "prop-types";
import React, { Component } from "react";

class HelloWorldText extends Component {

    render() {
        return (
            <div>
                <div>Hello {this.props.name}</div>
            </div>
        );
    }
}

HelloWorldText.propTypes = {
    name: PropTypes.string.isRequired
};

export default HelloWorldText;

Replace src/App.js by:

import React, { Component } from 'react';

import HelloWorldText from './HelloWorldText';

class App extends Component {
    render() {
        return (<HelloWorldText name="Martin" />);
    }
}

export default App;

You may replace Martin by your name.

Run in react-app folder:

npm run build && node app.js

and open then http://localhost:4711 in your browser to test the result.

From now on, you can develop with React. Follow the tutorial to learn more about React.

React Router

In the single page application the express router provides one and only one HTML page. Normally, you have different websites that are reachable via different URIs. The React Router is a library that attaches URIs to a corresponding React component. Hence, if the user enters an URI the React Router/React renders the according component. So we need just to write one component per page and to inform the router which URI should be matched to which component.

In src create a Hello.js:

import React, { Component } from 'react';

import HelloWorldText from './HelloWorldText.js';

class Hello extends Component {
    render() {
        return (<HelloWorldText name="Martin" />);
    }
}

export default Hello;

In src create a GoodBye.js:

import React, { Component } from 'react';

class GoodBye extends Component {
    render() {
        return (<h1>Good Bye!</h1>);
    }
}

export default GoodBye;

Adapt in src the App.js to use the router:

import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from "react-router-dom";

import GoodBye from './GoodBye';
import Hello from './Hello';
import NavigationBar from './NavigationBar';

class App extends Component {
    render() {
        return (
            <Router>
                <div>
                    <NavigationBar />
                    <Route path="/hello" component={Hello} />
                    <Route path="/goodbye" component={GoodBye} />
                </div>
            </Router>
        );
    }
}

export default App;

Let’s define the extremly primitive NavigationBar component that we have used in the above App.js. Create src/NavigationBar.js:

import React, {Component} from 'react';
import { Link } from 'react-router-dom'

class NavigationBar extends Component {
    render() {
        return (
            <ul>
                <li>
                    <Link to="/hello">Hello</Link>
                </li>
                <li>
                    <Link to="/goodbye">GoodBye</Link>
                </li>
            </ul>
        );
    }
}

export default NavigationBar

In react-app folder add React Router as dependency to the application:

npm install react-router-dom --save

Let’s run the example in the react-app folder:

npm run build && node app.js

http://localhost:4711/goodbye in the browser displays something like:

If you enter the path /hello the hello page is displayed. If you enter any path other than /hello or /goodbye just the navigation bar is displayed.

File Tree

The files of the above example:

├── app.js
├── build
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
├── README.md
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── GoodBye.js
    ├── Hello.js
    ├── HelloWorldText.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    └── NavigationBar.js

You find the complete source code here.

Without react-scripts

While this blog post might not be updated npx create-react-app will provide you the latest official way how to setup a React application. If you need more control how to steer your project and you do not want to use react-scripts, you can remove react-scripts from an existing project that has been created by npx create-react-app by running the command:

npm run eject

You cannot undo that step!. It creates all the dev dependencies and adapts the environment that you can do the same things like before, but without react-scripts.

If you like to start from scratch without react-scripts, you will need:

  • a transpiler that translates JSX that cannot be intepreted by a browser to JavaScript (e.g. babel/preset-react).
  • a transpiler that translates your current JavaScript version to an old version that all browsers support (e.g. ES6 to ES5 with babel/preset-env).
  • a bundler that bundles all the JavaScript files with the index.js as entry point to one bundle.js that will be delivered via your server to the browser along with assets and the index.html file that uses the bundle.js.

Here the steps, how to achieve that:

mkdir react-app && cd $_
npm init -y

which creates a package.json next to the src folder. Within this package.json inside the scripts object and above test we add the following line:

"build": "webpack --mode production",

Resulting in the following package.json:

{
  "name": "react-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack --mode production",
    "test": "echo \"Error: no test specified\" && exit 1",
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Webpack assumes per default the sources to be in src and that it should place the bundle into dist. Run in react-app folder:

mkdir src dist
npm install webpack webpack-cli --save-dev
npm install @babel/core babel-loader @babel/preset-env @babel/preset-react --save-dev

Create .babelrc in react-app folder:

{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

Create webpack.config.js in react-app folder:

const HtmlWebPackPlugin = require('html-webpack-plugin');

module.exports = {
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                },
            },
            {
                test: /\.html$/,
                use: [
                    {
                        loader: 'html-loader',
                    },
                ],
            },
        ],
    },
    plugins: [
        new HtmlWebPackPlugin({
            template: './src/index.html',
            filename: './index.html',
            favicon: 'src/favicon.ico',
        }),
    ],
};

It is standard that a website has an icon. Webpack adds <link rel="shortcut icon" href="favicon.ico"> to our index.html. If you do not have one, you may export any image as ico using gimp or take this one. Place favicon.ico next to the index.html that you create also in the src folder:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Hello World</title>
</head>
<body>
    <div id="root" />
    <div>No React available!</div>
</body>
</html>

In src create a App.js that we can test the setup:

import React, { Component } from 'react';

class App extends Component {
    render() {
        return (
            <div>Bonjour les gars!</div>
        );
    }
}

export default App;

as well as an index.js in src:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

In react-app folder:

npm install html-webpack-plugin html-loader --save-dev
npm install react react-dom --save
npm run build

You find the bundle called main.js and index.html in dist. Follow the instructions above in the blog post, how to setup express and how to write the React source code, where you replace, of course, build by dist in the app.js.

You find the complete source code of this approach here.

TAGS

Comments

Please accept our cookie agreement to see full comments functionality. Read more