3. Main Page & Routing

Main pages and Routing

For this application we'll need 4 routes:

  • "/" for the Home page

  • "/login" for the Login page

  • "/signup" for the Signup page

  • "/authentication" to use it for the authentication callback URL

First, we'll need to install the react-router and react-router-dom dependencies:

npm install react-router react-router-dom

Create a new file Routing.js in the app/ folder and we can include the first Route to the Home page:

// src/app/Routing.js
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import HomePage from "../features/Home/HomePage";

function Routing() {
    return (
        <Router>
            <Switch>
                <Route exact path="/" component={HomePage} />
            </Switch>
        </Router>
    );
}

export default Routing;

Basic components

Home Page

In the feature folder, create a Home/ subfolder in the src/features/ folder with the HomePage.js and HomePage.scss files:

// src/features/Home/HomePage.js
import React from 'react';
import './HomePage.scss';

function HomePage() {

    return (
        <div className="home-wrapper"></div>
    );
}

export default HomePage;
// src/features/Home/HomePage.scss
.home-wrapper {
    min-height: 100%;
    background-size: cover;
    background-image: linear-gradient(to bottom, rgba(74, 85, 107, 0.2), rgba(0, 0, 0, 0.66)), url('../../assets/images/background-image.jpg');
    -webkit-background-size: cover;
}

To add a basic navigation for the app, you'll need to create the Header component in the src/components/ folder. Let's create a subfolder header to include the JavaScript and CSS file.

// src/components/header/Header.js
import React from 'react';
import { NavLink } from 'react-router-dom';
import './Header.scss';

function Header() {
    return (
        <>
            <header className="header">
                <NavLink exact activeClassName="active" to="/">
                    Home
                </NavLink>

                <ul>
                    <li>
                        <NavLink activeClassName="active" to="/login">
                            Log in
                        </NavLink>
                    </li>
                    <li>
                        <NavLink activeClassName="active" to="/signup">
                            Create account
                        </NavLink>
                    </li>
                </ul>
            </header>
        </>
    );
}

export default Header;
// src/components/header/Header.scss
header {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    height: 6.4rem;
    border-bottom: 1px solid #657690;
    padding-left: 10%;
    padding-right: 10%;
    margin: 0 auto;
    background: #efefef;

    a {
        font-size: 1.6rem;
        text-decoration: none;
        color: #4a556b;
        outline: 0;

        &:hover {
            color: #252736;
        }
    }

    ul {
        list-style: none;
        align-self: end;
        height: 100%;
        padding: 2rem;

        li {
            display: inline-block;
            margin-left: 2rem;
        }
    }
}

Now, we can import the Header component in the already created Routing.js:

// src/app/Routing.js
...
// Use the following syntax to import the Header 
import Header from "../components/header/Header";

function Routing() {
    return (
        <Router>
            {/* Add the Header component as following */}
            <Header /> 
            <Switch>
                <Route exact path="/" component={HomePage} />
            </Switch>
        </Router>
    );
}

Login Page

In the same way, let's create the Login page in the features/login/ folder:

// src/features/Login/LoginPage.js
import React from 'react';

 function LoginPage() {

     return (
         <form className={'flex-column-wrapper flex-content-center flex-align-items-center'}>
             <div className={'flex-spacer-small'} />

             <h3>Log in</h3>

             <div className={'flex-spacer-small'} />

             <input
                 name={'username'}
                 type={'text'}
                 placeholder="Username"
                 autoComplete={'username'}
             />

             <div className={'flex-spacer-large'} />

             <button className={'btn btn-accent'} type={'submit'}>
                 Next
             </button>

             <div className={'flex-spacer-small'} />
         </form>
     );
 }

 export default LoginPage;

And include the "/login" Route into the Routing.js:

// src/app/Routing.js
import LoginPage from "../features/Login/LoginPage";

function Routing() {
    return (
        <Router>
            <Header />
            <Switch>
                <Route exact path="/" component={HomePage} />
                <Route path="/login" component={LoginPage} />
            </Switch>
        </Router>
    );
}

We can access the Login page on "/login" route or by clicking on the "Login" button.

The HTML form elements work a bit differently from other DOM elements in React. As in HTML, the form element keeps its own internal state, in React we have the option to keep the state in the Component level. For convenience, we can have a JavaScript function to handle the input changes and the submission of the form.

Since React 16.8, we can easily set the state in function components as following:

// Declare a new state variable, which we'll call "username"
const [username, setUsername] = useState('');

You can learn more about forms and hooks

// src/features/Login/LoginPage.js
import React, { useState } from 'react';

function LoginPage() {
    const [username, setUsername] = useState('');

    const handleChange = event => {
      setUsername(event.target.value);
    };

    const handleSubmit = event => {
        event.preventDefault();
    };

    return (
        <form className={'flex-column-wrapper flex-content-center flex-align-items-center'}
            onSubmit={e => handleSubmit(e)}
            >
            .
            .
            <input
                .
                .
                value={username}
                onChange={e => handleChange(e)}
            /
            .
            .
        </form>
    );
}

Next step is to add the validation for the username. Let's create a validation.js file in the utils/ folder.

The username must follow these rules:

  • start with a letter

  • contain alphanumeric characters only

  • contain lowercase letters only

  • be between 4 and 21 characters

// src/utils/validations.js
const HAT_NAME_REGEX = /^[a-z][a-z0-9]{2,19}[a-z0-9]$/;

export const isHatName = hatName => {
  return HAT_NAME_REGEX.test(hatName);
};

And you'll use this function to validate the user's input and display an error message when the username is not valid to submit. In the LoginPage.js we can validate the user's input and display an error message when the username is not valid to submit. In the onChange method we can reset the error message.

// src/features/Login/LoginPage.js
import React, { useState } from 'react';
import { isHatName } from "../../utils/validations";

function LoginPage() {
    const [username, setUsername] = useState('');
    const [errorMsg, setErrorMsg] = useState('');

    const errorMessages = {
        usernameNotValid: 'Sorry, this username is not valid'
    };

    const handleChange = event => {
      setUsername(event.target.value);
      // Reset the error message when the user is start typing.
      setErrorMsg('');
      };

    const handleSubmit = event => {
        event.preventDefault();
        validateLoginDetails();
    };

    const validateLoginDetails = () => {
        // Validate the username and display an error message if it's not valid.
        if (isHatName(username)) {
            // TODO redirect the user to their PDA.
        } else {
            setErrorMsg(errorMessages.usernameNotValid);
        }
    };


    return (
        <form
            className={'flex-column-wrapper flex-content-center flex-align-items-center'}
            onSubmit={e => handleSubmit(e)}
        >
            .
            .
            <input
                className={` ${errorMsg ? 'input-error-field' : null}`}
                .
                .
            />
            {errorMsg && <div className={'input-error-label'}>{errorMsg}</div>}
            .
            .
        </form>
    );
}

Last updated