React Routing and Components for Signup and Login In this post, we will create a user interface in React for authentication (Signup and Login views). Components will not perform any actions (they won’t be communicating with the backend, yet). We will use code from the previous post: Starting SaaS with Django and React (code with tag v1). In the next posts, we will create an authentication REST API in Django and provide actions in the frontend.

What will we do in this post:

  • Create React components (Home, Signup, Login, Dashboard).
  • Add routing with react-router-dom package between views.
  • Initialize Redux structure in the project.

In this tutorial we will use bootstrap with react-bootstrap package to build frontend user interface. If you would like to see post how to use React with other than bootstrap packages, please let me know by filling the form. Other packages can be: Material, Ant, Bulma, or Tailwind.

Add React Components

Please open a new terminal window and navigate to the frontend directory. Then, please run the development server:

npm start

It has hot-reloading, which means that all changes (after file save) are forcing the server to reload and serve the newest version (so we don’t have to hit F5 constantly). You should see your frontend running at http://localhost:3000/.

Install packages

Please stop the dev server for a moment. We will need to install few new packages for start:

# for user interface
npm install react-bootstrap bootstrap
# for routing
npm install react-router-dom

(The react-router-dom documentation, if you need to check it). After installation please run again the dev server (npm start). You can also check the frontend/package.json file, the newly installed packages should be added there.

You need to add bootstrap.css import in frontend/src/index.js file.

// ...
// add this import in frontend/src/index.js
// add it before index.css import
import "bootstrap/dist/css/bootstrap.css";
// ...

Let’s clean our React app

The React app was generated with standard starting script. It has code that we will not need. Please remove the frontend/src/App.css file and remove all content in frontend/src/index.css (we will keep the file, maybe it will be needed in the future). Please also delete files:

  • frontend/public/logo192.png
  • frontend/public/logo512.png
  • frontend/src/logo.svg

Then, please update the frontend/public/manifest.json file:

{
  "short_name": "SaaSitive",
  "name": "SaaSitive Django+React Boilerplate",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}

You can use your values for short_name and name.

Let’s go to frontend/src/App.js, remove its content, and replace it with the following code:

import React, { Component } from "react";

class App extends Component {
  render() {
    return <h1>Hi!</h1>;
  }
}

export default App;

The development server should refresh the http://localhost:3000 website and display “Hi!”.

OK, let’s add components directory in frontend/src and Home.js file in it.

// frontent/src/components/Home.js
import React, { Component } from "react";
import { Container } from "react-bootstrap";

class Home extends Component {
  render() {
    return (
      <Container>
        <h1>Home</h1>
      </Container>
    );
  }
}

export default Home;

Wait … How to navigate to Home.js view? We will need a router! (It is already installed with react-router-dom package)

Let’s go to App.js file.

// frontend/src/App.js
import React, { Component } from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Home from "./components/Home";

class App extends Component {
  render() {
    return (
      <div>
        <BrowserRouter>
          <Switch>
            <Route exact path="/" component={Home} />
          </Switch>
        </BrowserRouter>
      </div>
    );
  }
}

export default App;

We added BrowsableRouter with Switch and one Route to path="/". It routes to our Home component. We can add more components. We will add them in separate directories, because later we will create actions and reducers for each (the Redux part).

Let’s add signup, login, dashboard directories in frontend/src/components.

Please add Signup component in the file frontend/src/componenets/signup/Signup.js:

// frontend/src/componenets/signup/Signup.js

import React, { Component } from "react";
import { Container } from "react-bootstrap";

class Signup extends Component {
  render() {
    return (
      <Container>
        <h1>Signup</h1>
      </Container>
    );
  }
}

export default Signup;

Please add Login component in the file frontend/src/componenets/login/Login.js:

// frontend/src/componenets/login/Login.js

import React, { Component } from "react";
import { Container } from "react-bootstrap";

class Login extends Component {
  render() {
    return (
      <Container>
        <h1>Login</h1>
      </Container>
    );
  }
}

export default Login;

Please add Dashboard component in the file frontend/src/componenets/dashboard/Dashboard.js:

// frontend/src/componenets/dashboard/Dashboard.js

import React, { Component } from "react";
import { Container } from "react-bootstrap";

class Dashboard extends Component {
  render() {
    return (
      <Container>
        <h1>Dashboard</h1>
      </Container>
    );
  }
}

export default Dashboard;

We have three new components. We will add them to our router.

Please update the frontend/src/App.js file:

// frontend/src/App.js file

import React, { Component } from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Home from "./components/Home";
import Signup from "./components/signup/Signup";
import Login from "./components/login/Login";
import Dashboard from "./components/dashboard/Dashboard";

class App extends Component {
  render() {
    return (
      <div>
        <BrowserRouter>
          <Switch>
            <Route path="/signup" component={Signup} />
            <Route path="/login" component={Login} />
            <Route path="/dashboard" component={Dashboard} />
            <Route exact path="/" component={Home} />
          </Switch>
        </BrowserRouter>
      </div>
    );
  }
}

export default App;

OK, it is time to test it! You can enter in the browser different paths and check if correct components are displayed. For example, please enter http://localhost:3000/login and you should see the Login component. The http://localhost:3000/dashboard should show a Dashboard component.

Maybe you wonder what is the difference between Route path and Route exact path (used for Home). You can try to check it yourself:

The first one will show you Signup component, while the second one will show a blank window. The first URL will match the Signup route. The second one won’t have match. If you would like to have a default match for any URL you can add the default route:

<Route path="*">Ups</Route>

It will display “Ups” for unknown matches.

Your fronted code structure should look like below after this part of the post:

.
├── package.json
├── package-lock.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── manifest.json
│   └── robots.txt
├── README.md
├── src
│   ├── App.js
│   ├── App.test.js
│   ├── components
│   │   ├── dashboard
│   │   │   └── Dashboard.js
│   │   ├── Home.js
│   │   ├── login
│   │   │   └── Login.js
│   │   └── signup
│   │       └── Signup.js
│   ├── index.css
│   ├── index.js
│   ├── reportWebVitals.js
│   └── setupTests.js
└── yarn.lock

React components

Let’s add more code to the components. We will make Home.js as the main view with links to the other views:

// frontend/src/components/Home.js

import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Container } from "react-bootstrap";

class Home extends Component {
  render() {
    return (
      <Container>
        <h1>Home</h1>
        <p>
          <Link to="/login/">Login</Link>
        </p>
        <p>
          <Link to="/signup">Sign up</Link>
        </p>
        <p>
          <Link to="/dashboard">Dashboard</Link>
        </p>
      </Container>
    );
  }
}

export default Home;

Please take a look that we are using Link from react-router-dom package for creating links. These links will use router to navigate to the correct view.

Now, we will define the user interface for creating the user account. Let’s add more code to Signup.js file.

// frontend/src/components/signup/Signup.js

import React, { Component } from "react";
import { Link } from "react-router-dom";
import {
  Container,
  Button,
  Row,
  Col,
  Form,
  FormControl
} from "react-bootstrap";

class Signup extends Component {
  constructor(props) {
    super(props);
    this.state = {
      username: "",
      password: ""
    };
  }
  onChange = e => {
    this.setState({ [e.target.name]: e.target.value });
  };

  onSignupClick = () => {
    const userData = {
      username: this.state.username,
      password: this.state.password
    };
    console.log("Sign up " + userData.username + " " + userData.password);
  };

  render() {
    return (
      <Container>
        <Row>
          <Col md="4">
            <h1>Sign up</h1>
            <Form>
              <Form.Group controlId="usernameId">
                <Form.Label>User name</Form.Label>
                <Form.Control
                  type="text"
                  name="username"
                  placeholder="Enter user name"
                  value={this.state.username}
                  onChange={this.onChange}
                />
                <FormControl.Feedback type="invalid"></FormControl.Feedback>
              </Form.Group>

              <Form.Group controlId="passwordId">
                <Form.Label>Your password</Form.Label>
                <Form.Control
                  type="password"
                  name="password"
                  placeholder="Enter password"
                  value={this.password}
                  onChange={this.onChange}
                />
                <Form.Control.Feedback type="invalid"></Form.Control.Feedback>
              </Form.Group>
            </Form>
            <Button 
              color="primary"
              onClick={this.onSignupClick}  
            >Sign up</Button>
            <p className="mt-2">
              Already have account? <Link to="/login">Login</Link>
            </p>
          </Col>
        </Row>
      </Container>
    );
  }
}

export default Signup;

We use bootstrap forms to create signup form. It is only a visual part of it. It is not going to perform any actions, but we will add it soon (in the next posts). If you will write some username and password and click on Signup button, you should see some logs in your console (please open web tools to check console). There are two variables in the component’s state: username and password. Both variables are connected with input fields.

Please take a notice that only username and password is required to create the account. In the future post, there will be an example where user name, email address, and password will be needed (the login will be based on email + password).

The code for Login.js (which is very similar to Signup.js):

// frontend/src/components/login/Login.js

import React, { Component } from "react";
import { Link } from "react-router-dom";
import {
  Container,
  Button,
  Row,
  Col,
  Form,
  FormControl
} from "react-bootstrap";

class Login extends Component {
  constructor(props) {
    super(props);
    this.state = {
      username: "",
      password: ""
    };
  }
  onChange = e => {
    this.setState({ [e.target.name]: e.target.value });
  };

  onLoginClick = () => {
    const userData = {
      username: this.state.username,
      password: this.state.password
    };
    console.log("Login " + userData.username + " " + userData.password);
  };
  render() {
    return (
      <Container>
        <Row>
          <Col md="4">
            <h1>Login</h1>
            <Form>
              <Form.Group controlId="usernameId">
                <Form.Label>User name</Form.Label>
                <Form.Control
                  type="text"
                  name="username"
                  placeholder="Enter user name"
                  value={this.state.username}
                  onChange={this.onChange}
                />
                <FormControl.Feedback type="invalid"></FormControl.Feedback>
              </Form.Group>

              <Form.Group controlId="passwordId">
                <Form.Label>Your password</Form.Label>
                <Form.Control
                  type="password"
                  name="password"
                  placeholder="Enter password"
                  value={this.state.password}
                  onChange={this.onChange}
                />
                <Form.Control.Feedback type="invalid"></Form.Control.Feedback>
              </Form.Group>
            </Form>
            <Button color="primary" onClick={this.onLoginClick}>Login</Button>
            <p className="mt-2">
              Don't have account? <Link to="/signup">Signup</Link>
            </p>
          </Col>
        </Row>
      </Container>
    );
  }
}

export default Login;

After above steps you should have following views:

Add Redux to React

Redux will help us organize and manage the application data. It consists of three main concepts:

  • Store which keeps information about application state (data),
  • Reducer that specify how the state is changed in response to actions,
  • Actions the source of information for the Store.

Before adding new code, let’s install necessary packages:

npm install redux react-redux redux-thunk connected-react-router

First we will define the Reducer. Please add Reducer.js file in frontend/src directory:

// frontend/src/Reducer.js

import { combineReducers } from "redux";
import { connectRouter } from "connected-react-router";

const createRootReducer = history =>
  combineReducers({
    router: connectRouter(history)
  });

export default createRootReducer;

If we will create a new reducer for a component we will add it here. It is the main reducer that concatenates all component’s reducers from the application.

Then, we need to add Store to our application. Please add Root.js file in frontend/src directory:

// frontend/src/Root.js

import React from "react";
import thunk from "redux-thunk";
import { Provider } from "react-redux";
import { createBrowserHistory } from "history";
import { applyMiddleware, createStore } from "redux";
import { routerMiddleware, ConnectedRouter } from "connected-react-router";

import rootReducer from "./Reducer";

const Root = ({ children, initialState = {} }) => {
  const history = createBrowserHistory();
  const middleware = [thunk, routerMiddleware(history)];

  const store = createStore(
    rootReducer(history),
    initialState,
    applyMiddleware(...middleware)
  );

  return (
    <Provider store={store}>
      <ConnectedRouter history={history}>{children}</ConnectedRouter>
    </Provider>
  );
};

export default Root;

The last change will replace Router with Root component in App.js.

// frontend/src/App.js

import React, { Component } from "react";
import Root from "./Root"; // <------------- new import
import { Route, Switch } from "react-router-dom"; // <--- remove BrowserRouter
import Home from "./components/Home";
import Signup from "./components/signup/Signup";
import Login from "./components/login/Login";
import Dashboard from "./components/dashboard/Dashboard";

class App extends Component {
  render() {
    return (
      <div>
        <Root> {/* replace BrowserRouter with Root */}
          <Switch>
            <Route path="/signup" component={Signup} />
            <Route path="/login" component={Login} />
            <Route path="/dashboard" component={Dashboard} />
            <Route exact path="/" component={Home} />
            <Route path="*">Ups</Route>
          </Switch>
        </Root> {/* replace BrowserRouter with Root */}
      </div>
    );
  }
}

export default App;

The application should run without any changes in the behavior. We’ve just added empty skeleton for Redux. We will add more to it in the next posts.

Commit code to the repository

Please remember to commit all code changes to the repository:


git add src/components/
git add src/Reducer.js
git add src/Root.js
git add package-lock.json 

git commit -am "add signup and login components"

git push

Summary

  • We added new components (Home, Signup, Login, and Dashboard) to the fronted.
  • There is routing defined between component’s views.
  • The Redux skeleton was added to the React application.

What’s next?

We will add authentication REST API in the backend. We will use Token-based authentication from Django Rest Framework and Djoser package. Our authentication REST API will be able to:

  • create (signup) a new user,
  • login and logout the user,
  • return user information.

Next article: Token Based Authenitcation with Django Rest Framework and Djoser


Let's stay in touch!

Would you like to be notified about new posts? Please fill this form.


Boilerplate Tutorial Articles:
1. Starting SaaS with Django and React
2. React Routing and Components for Signup and Login (↩ you are here!)
3. Token Based Authentication with Django Rest Framework and Djoser
4. React Token Based Authentication to Django REST API Backend
5. React Authenticated Component
6. CRUD in Django Rest Framework and React
7. Docker-Compose for Django and React with Nginx reverse-proxy and Let's encrypt certificate
8. Django Rest Framework Email Verification
9. Django Rest Framework Reset Password
More articles coming soon!

Link to the code repository: saasitive/django-react-boilerplate