Series: Building a Social Network with Flask, React & Stream – Part 13

Spencer P.
Spencer P.
Published April 6, 2020 Updated April 28, 2020

This article is the second installment of a tutorial series focused on how to create a full-stack application using Flask, React/Redux and Stream. In this piece, we are going to be starting our authentication flow with registration and user confirmation, as well as getting up and running with React-Redux. Be sure to check out the repo to follow along!

Before We Begin

This week, we are going to be diving into developing with React and Redux. Before we get started, I want to explain the reason behind some of the choices in how we are going to build our application. The React component of this tutorial serves as a bridge for those coming from a 'vanilla' HTML/CSS/JS deployment of Flask. The learning curve behind some aspects of React can be daunting to newer users, especially in the context of integrating with a Flask backend. Therefore, I try to keep as many familiar parts as possible to flatten the learning curve and get you up and running with full-stack development with Stream as quickly as possible. As a warning, some of the concepts we use are out of convenience, so be sure to air on the side of caution when deploying a production application.

Getting Started

First, we navigate to our React application. Our React app is based in our static directory, which we can jump to in the command line using 'cd app/static'.

Since this project is complex and involves multiple pages (or collections of components based on routes), we need to install React-Router, which handles rendering components based on a URL path. We can do this step with ‘npm install react-router-dom` in the CLI. Next, we will create a JSX file to specifically handle the routing in our application, named AppRouter (in 'app/static/js/AppRouter.jsx')

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


class AppRouter extends Component {

    render() {
        return (
            <HashRouter>
                <div className="app"></div>
            </HashRouter>
        )
    }
}

export default AppRouter

Statecraft

Managing state throughout multiple components in our application is a central challenge in creating our app. Rather than trying to pass that state through multiple components and attempting to keep track of those changes, it would be easier and cleaner to use a state management library such as Redux. If you are new to React, Redux can seem like a complicated concept, but the idea is quite simple. Rather than trying to manage state and its changes by passing it through multiple components individually, Redux allows you to have a centralized store to save and access state, updating multiple components simultaneously.

Using Redux involves a fair bit of programming overhead. We'll start by installing the modules with ‘npm install react-redux redux'.

Reductionist Tendencies

We want to give our application a name that users see when they first access the site. This aspect creates a convenient entry point for setting up Redux. Redux modifies state with functions known as reducers. Reducers take an action (dispatched by a component) and update the stored state. Don't stress too much if these concepts don't make sense immediately- it all becomes more clear as the tutorial goes on.

We create our first reducer in a reducers directory (in `app/static/js/reducers/common.jsx)

const defaultState = {
    appName: 'Offbrand'
};

export default (state = defaultState) => {
    return state;
}

This file creates a default state object, appName, and passes it to the reducer to create the initial state.

Provisions

Next, we create a store file that combines any reducers that we create, which are then exported to use in a provider (in 'app/static/js/store.jsx')

import { combineReducers, createStore } from 'redux';


import common from './reducers/common';

const reducer = combineReducers({
    common
});

const store = createStore(reducer);

export default store

A provider gives access to the state that we have established as a context throughout the components of our application. We can wrap our AppRouter tag in a newly created JSX file named App to allow our application to access the store. (in ‘app/static/js/App.jsx’)

import React from "react";
import { Provider } from 'react-redux';
import AppRouter from './AppRouter';
import store from './store'



const App = () => (
    <Provider store={store}>
        <AppRouter />
    </Provider>
);

export default App;

The provider supplies the store that we just created to the rest of the application, allowing components to dispatch events, as well as retrieve state from the store. After, we import the App in our index file to render our application to the page (in 'app/static/js/index.jsx).

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

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

State Property

Now that we have created the store and provided it to the app, we can start working to access those elements. React retrieves elements from the store by using MapStateToProps, and connecting that function with the component. We implement this in our AppRouter component so you can get a feel for how it works (in 'app/static/js/AppRouter.jsx`).

import React, { Component } from "react";
import { HashRouter } from 'react-router-dom';
import { connect } from 'react-redux';


const mapStateToProps = state => {
    return {
        appName: state.common.appName
    }
};

class AppRouter extends Component {

    render() {
        return (
            <HashRouter>
                <div className="app">{this.props.appName}</div>
            </HashRouter>
        )
    }
}

export default connect(mapStateToProps)(AppRouter);

In the MapStateToProps function, you can see that we associate 8appName with the value that we initialized in our common reducer. After connecting the mapping with the AppRouter* component, we can now access that state as a property.

Heading Out

Now that we have these basics covered, we can start moving into our first "true" component of the application: the header. The header displays our app title, as well as a conditional rendering for login, registration, settings, and the user profile dependent on whether or not a user is authenticated. To keep ourselves organized, we create a new 'components folder' with a header component contained within it. As we don’t currently have any methods for authentication, we need to create our LoggedOutView variable to render (in ‘app/js/static/js/components/Header.jsx’).

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

const LoggedOutView = props => {
    if (!props.currentUser) {
        return (
            <ul className="nav navbar-nav pull-xs-right">
                <li className="nav-item">
                    <Link to="/" className="nav-link">
                        Home
                    </Link>
                </li>

                <li className="nav-item">
                    <Link to="login" className="nav-link">
                        Sign in
                    </Link>
                </li>

                <li className="nav-item">
                    <Link to="register" className="nav-link">
                        Sign up
                    </Link>
                </li>
            </ul>
        );
    }
    return null;
};

class Header extends React.Component {
    render() {
        return (
            <nav className="navbar navbar-light">
                <div className="container">
                    <Link to="/" className="navbar-brand">
                        {this.props.appName}
                    </Link>

                    <LoggedOutView currentUser={this.props.currentUser}/>

                </div>
            </nav>
        )
    }
}

export default Header

Once that step is done, we can import this component to AppRouter to be rendered. Since we call on the currentUser prop within the LoggedOutView, we will add it in MapStateToProps(in app/static/js/AppRouter.jsx).

#...

import Header from './components/Header'

const mapStateToProps = state => {
  return {
    #...
    currentUser: state.common.currentUser
  }
};

class AppRouter extends Component {
  
  render() {
    return (
      <HashRouter>
        <div className="app">
          <Header
            currentUser={this.props.currentUser}
            appName={this.props.appName}/>
        </div>
      </HashRouter>
      )
   }
}

#...

Super-Agency

Now that we have currentUser, we need a way to 'provide' it. First, we create a way to request information from our backend. We use the Superagent library to handle our requests, which allows us to avoid any difficulties from the deprecation of the request. To install it, we use ‘npm install superagent superagent-promise' in our CLI.

Next, we create a file to hold the functions for making requests (in app/static/js/agent.jsx).

import _superagent from 'superagent';
import superagentPromise from 'superagent-promise';

const superagent = superagentPromise(_superagent, global.Promise);

const API_ROOT = 'http://127.0.0.1:5000';

const responseBody = res => res.body;

const agent = {
    get: url =>
        superagent.get(`${API_ROOT}${url}`).then(responseBody),
    post: (url, form) =>
        superagent.post(`${API_ROOT}${url}`).type('form').send(form).then(responseBody)
};

const Auth = {
    current: () =>
        agent.get('/user'),
    register: (email, username, password, password2) =>
        agent.post('/auth/register', { email, username, password, password2 }),
};

export default {
    Auth
}

This file contains the root URL for our backend, constructors for our requests, and a handler for the response. I have jumped ahead on a few of our endpoints; however, when simultaneously developing both frontend and backend, you often find yourself in chicken-and-egg scenarios like this one. I personally prefer to create the frontend components first and then circle back to create the endpoints to support it. Additionally, for our 'POST' method, I have added our information as a form instead of JSON. This information will make a lot more sense once we head back to our backend.

A Redux Redux

Since we are handling state changes from our authentication requests, we create an auth reducer for them (in app/static/js/reducers/auth.jsx).

export default (state = {}, action) => {
    switch (action.type) {
        case 'REGISTER':
            return {
                ...state,
                inProgress: false,
                errors: action.error ? action.payload.errors : null
            };
        case 'UPDATE_FIELD_AUTH':
            return {...state, [action.key]: action.value};
        case 'ASYNC_START':
            if (action.subtype === 'REGISTER') {
                return { ...state, inProgress: true };
            }
            break;
        default:
            return state;
    }
    return state;
}

This step handles actions from updating our form, as well as submitting the form to the server, and any errors that are returned by it.

We have to make a few changes to our common reducer as well for these actions (in app/static/js/reducers/common.jsx)

const defaultState = {
    appName: 'Offbrand'
};

export default (state = defaultState, action) => {
    switch(action.type) {
        case 'APP_LOAD':
            return {
                ...state,
                appLoaded: true,
                currentUser: action.payload ? action.payload.user : null
            };
        case 'REDIRECT':
            return {...state, redirectTo: null};
        case 'REGISTER':
            return {
                ...state,
                redirectTo: action.error ? null : '/',
                currentUser: action.error ? null : action.payload.user,
            };
        default:
            return state;
    }
}

Getting In The Middle

Once a user is registered, or later logged in/out, we need a way to connect the requests with our store and its corresponding actions. We also need to be able to store the user object that we receive from the backend in the local storage of the browser to help persist the session and avoid repeated requests (in app/static/js/middleware.jsx).

const promiseMiddleware = store => next => action => {
    if (isPromise(action.payload)) {
        store.dispatch({type: 'ASYNC_START', subtype: action.type});
        action.payload.then(
            res => {
                action.payload = res;
                store.dispatch(action);
            },
            error => {
                action.error = true;
                action.payload = error.response.body;
                store.dispatch(action);
            }
        );

        return;
    }
    next(action);
};

function isPromise(v) {
    return v && typeof v.then === 'function';
}

const localStorageMiddleware = store => next => action => {
    if (action.type === 'REGISTER' || action.type === 'LOGIN') {
        if (!action.error) {
            window.localStorage.setItem('currentUser', action.payload.user);
            store.redirectTo = '/'
        }
    } else if (action.type === 'LOGOUT') {
        window.localStorage.setItem('currentUser', '');
    }

    next(action);
};

export {
    localStorageMiddleware,
    promiseMiddleware
}

Now that we have created our middleware and auth reducer, we need to apply it to our store. We also have a bit of housework to take care of in accommodating React-Router with Redux, as well as creating a hash history to help us navigate redirects following a successful registration. First, we install the packages in the CLI with ‘npm install react-router-redux history. Next, we create the file to use these packages (in app/static/js/store.jsx`).

import { applyMiddleware, combineReducers, createStore } from 'redux';
import { localStorageMiddleware, promiseMiddleware} from "./middleware";
import { routerMiddleware, routerReducer } from 'react-router-redux'
import createHistory from 'history/createHashHistory';

import auth from './reducers/auth';
import common from './reducers/common';

export const history = createHistory();

const myRouterMiddleware = routerMiddleware(history);

const reducer = combineReducers({
    auth,
    common,
    router: routerReducer
});

const middleware = applyMiddleware(myRouterMiddleware, promiseMiddleware, localStorageMiddleware);

const store = createStore(reducer, middleware);

export default store

Register Component

Our registration component will encompass many of the topics we’ve covered already, like connect and mapStateToProps, as well as introducing some new ones, like mapDispatchToProps and form (in app/static/js/components/auth/Register.jsx).

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import agent from '../../agent'
import { connect } from 'react-redux'

const mapStateToProps = state => ({ ...state.auth });

const mapDispatchToProps = dispatch => ({
    onChangeEmail: value =>
        dispatch({type: 'UPDATE_FIELD_AUTH', key: 'email', value}),
    onChangeUsername: value =>
        dispatch({type: 'UPDATE_FIELD_AUTH', key: 'username', value}),
    onChangePassword: value =>
        dispatch({type: 'UPDATE_FIELD_AUTH', key: 'password', value}),
    onChangePassword2: value =>
        dispatch({type: 'UPDATE_FIELD_AUTH', key: 'password2', value}),
    onSubmit: (email, username, password, password2) => {
        const payload = agent.Auth.register(email, username, password, password2);
        dispatch({type: 'REGISTER', payload})
    }
});

class Register extends Component {
    constructor() {
        super();
        this.changeEmail = event => this.props.onChangeEmail(event.target.value);
        this.changeUsername = event => this.props.onChangeUsername(event.target.value);
        this.changePassword = event => this.props.onChangePassword(event.target.value);
        this.changePassword2 = event => this.props.onChangePassword2(event.target.value);
        this.submitForm = (email, username, password, password2) => event => {
            event.preventDefault();
            this.props.onSubmit(email, username, password, password2);
        }
    }

    render() {
        const {email, username, password, password2} = this.props;

        return (
            <div className="auth-page">
                <div className="container">
                    <div className="row">
                        <div className="col-md-6 offset-md-3 col-xs-12">

                            <h1 className="text-xs-center">Sign Up</h1>
                            <p className="text-xs-center">
                                <Link to="login">
                                    Have an Account?
                                </Link>
                            </p>

                            <form onSubmit={this.submitForm(email, username, password, password2)}>
                                <fieldset>

                                    <fieldset className="form-group">
                                        <input
                                            className="form-control form-control-lg"
                                            type="email"
                                            placeholder="Email"
                                            value={this.props.email || ""}
                                            onChange={this.changeEmail}/>
                                    </fieldset>

                                    <fieldset className="form-group">
                                        <input
                                            className="form-control form-control-lg"
                                            type="text"
                                            placeholder="Username"
                                            value={this.props.username || ""}
                                            onChange={this.changeUsername}/>
                                    </fieldset>

                                    <fieldset className="form-group">
                                        <input
                                            className="form-control form-control-lg"
                                            type="password"
                                            placeholder="Password"
                                            value={this.props.password || ""}
                                            onChange={this.changePassword}/>
                                    </fieldset>

                                    <fieldset className="form-group">
                                        <input
                                            className="form-control form-control-lg"
                                            type="password"
                                            placeholder="Your Password Again"
                                            value={this.props.password2 || ""}
                                            onChange={this.changePassword2}/>
                                    </fieldset>

                                    <button
                                        className="btn btn-lg btn-primary pull-xs-right"
                                        type="submit"
                                        disabled={this.props.inProgress}>
                                        Sign Up
                                    </button>

                                </fieldset>
                            </form>
                        </div>

                    </div>
                </div>
            </div>
        );
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(Register);

mapDispatchToProps is where we create functions that 'dispatch' events to our store, updating the state. Each event that is dispatched has an associated 'type'', and typically values that are associated with it have to be updated. The event is sent to the reducer, which, using the switch statement, performs the relevant action. UPDATE_FIELD_AUTH adjusts the values stored for each individual input, while REGISTER takes the form and sends it to our backend using our agents. Finally, mapStateToProps keeps the props in the component synced to those changes in the state.

Forms are obviously quite a bit more involved in React if you are coming from HTML, and the learning curve for them can be obnoxiously steep for what seems like a simple element. But since nearly every site you make includes at least one, you get used to the logic rather quickly.

Building your own app? Get early access to our Livestream or Video Calling API and launch in days!

Errors

While our form has some simple client-side validation for input values, it's always best practice to have some kind of server-based validation as well, as a malicious actor could simply send a form request to the backend directly. We need to set up server-side validation logic soon, but before we do, we can prepare ourselves on the frontend to handle them with a new component (in app/static/js/components/ListErrors.jsx).

import React from 'react';

class ListErrors extends React.Component {
    render() {
        const { errors } = this.props;
        if (errors) {
            return (
                <ul className="error-messages">
                    {
                        Object.keys(errors).map(key => {
                        return (
                            <li key={key}>
                                {key}: {errors[key]}
                            </li>
                        );
                    })
                }
            </ul>
        );
        } else {
            return null;
        }
    }
}

export default ListErrors

Errors always display through a component (the one that is making the request) so we can pass the state directly through props. After this step, the logic is simple: if there are errors, map them and render the key with the error. Next we must integrate it with our Register component (in app/static/js/components/auth/Register.jsx).

#...

import ListErrors from '../ListErrors';

#...

class Register extends Component {
  #...
  
  render() {
    #...
        </p>

        <ListErrors errors={this.props.errors}/>

        <form onSubmit={this.submitForm(email, username, password, password2)}>
    #...
                             

Finally, we add our registration component to AppRouter. We also need to add a mapDispatchToProps function and connect it to the component to handle redirect actions coming from registration to avoid an infinite loop of redirection (in app/static/js/AppRouter.jsx).

import React, { Component } from "react";
import { HashRouter, Route, Switch } from 'react-router-dom';
import { connect } from 'react-redux';
import agent from './agent'
import store from './store'
import { push } from 'react-router-redux'


import Header from './components/Header'
import Register from './components/auth/Register.jsx'

const mapStateToProps = state => {
    return {
    appName: state.common.appName,
    currentUser: state.common.currentUser,
    redirectTo: state.common.redirectTo
}};

const mapDispatchToProps = dispatch => ({
    onLoad: (payload, user) =>
        dispatch({ type: 'APP_LOAD', payload, user}),
    onRedirect: () =>
        dispatch({ type: 'REDIRECT'})
});


class AppRouter extends Component {

    componentWillReceiveProps(nextProps) {
        if (nextProps.redirectTo) {
            store.dispatch(push(nextProps.redirectTo));
            this.props.onRedirect();
        }
    }

    componentDidMount() {
        const user = window.localStorage.getItem('currentUser');
        this.props.onLoad(user ? agent.Auth.current() : null, user)
    }


    render() {
        return (
            <HashRouter>
                <div className="app">
                    <Header
                        currentUser={this.props.currentUser}
                        appName={this.props.appName}/>
                    <Switch>
                        <Route path="/register" component={Register} />
                    </Switch>
                </div>
            </HashRouter>
        )
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(AppRouter);

The componentDidMount lifecycle method checks to see if a user session is already active and load that user into state when the site is rendered.

Logged In View

Since a user can now register, theoretically, they are logged in once the registration process is over. Before we head to the backend to create our server-side endpoints, we can quickly create these different views to help let us know that the application is loading and handling the currentUser property properly. We start with the Header (in 'app/static/js/components/Header.jsx')

#...

const LoggedInView = props => {
    if (props.currentUser) {
        return (
            <ul className="nav navbar-nav pull-xs-right">
                <li className="nav-item">
                    <Link to="/" className="nav-link">
                        Home
                    </Link>
                </li>

                <li className="nav-item">
                    <Link to="settings" className="nav-link">
                        Settings
                    </Link>
                </li>

                <li className="nav-item">
                    <Link to={`@${props.currentUser.username}`}
                          className="nav-link">
                        <img src={props.currentUser.image} alt="none" className="user-pic" />
                    </Link>
                </li>
            </ul>
        )
    }

    return null;
};

class Header extends React.Component {
    render() {
        return (
            <nav className="navbar navbar-light">
                <div className="container">
                    <Link to="/" className="navbar-brand">
                        {this.props.appName}
                    </Link>

                    <LoggedOutView currentUser={this.props.currentUser}/>
                    
                    <LoggedInView currentUser={this.props.currentUser} />
                
                </div>
            </nav>
        )
    }
}

After, we create a home component to greet our users, with their username conditionally rendered once authenticated (in 'app/static/js/components/Home.jsx').

import React, { Component } from 'react';


const LoggedOutView = props => {
    if (!props.currentUser) {
        return (
            <h4>Welcome to Offbrand!</h4>
        )
    }
    return null;
};

const LoggedInView = props => {

    if (props.currentUser) {
        return (
            <h4>Welcome back, {props.currentUser.name ? props.currentUser.name : props.currentUser.username}</h4>
        )
    }
    return null;
};

class Home extends Component {

    render() {
        return (
            <div className="container page">
                <LoggedOutView currentUser={this.props.currentUser} />

                <LoggedInView currentUser={this.props.currentUser} />
            </div>
            )
        }
    }

export default Home

Finally, we will import this module to AppRouter and insert it into our render (in 'app/static/js/AppRouter.jsx').

#...

import Home from './components/Home'
#...

class AppRouter extends Component {
  #...
  render() {
    #...
          <Switch>
              <Route exact path="/" component={() => <Home currentUser={this.props.currentUser} />} />
              <Route path="/register" component={Register} />
          </Switch>
    #...

Next Steps

Now that we have created this section of the frontend for the project, we can start to put together the endpoints on the backend to service the requests from the front. First, create an auth directory and initialize it (in 'app/auth/init.py').

from flask import Blueprint

auth = Blueprint('auth', __name__)

from . import views

Server Side Validation!

Next, we need to handle our registration form using Flask-WTF. Setting our inputs as a form in our requests allows us to use Flask-WTF to validate these requests on the server-side, saving us tons of time creating our own logic for it.

We are trying to keep the learning curve as low as possible; therefore, I have manually set the CSRF token to false. However, if you were looking to deploy into production, simply pass the CSRF token as a jinja2 variable in the index, retrieving and setting it as a X-CSRFToken header for any 'POST' requests. Other than that, our form will be remarkably similar to before (in 'app/auth/forms.py').

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired, Length, Email, Regexp, EqualTo
from wtforms import ValidationError
from ..models import User


class RegistrationForm(FlaskForm):
    class Meta:
        csrf = False

    email = StringField('email', validators=[DataRequired(), Length(1, 64),
                                             Email()])
    username = StringField('username', validators=[
        DataRequired(), Length(1, 64),
        Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0,
               'Usernames must have only letters, numbers, dots or '
               'underscores')])
    password = PasswordField('password', validators=[
        DataRequired(), EqualTo('password2', message='Passwords must match.')])
    password2 = PasswordField('password2', validators=[DataRequired()])

    def validate_email(self, field):
        if User.query.filter_by(email=field.data.lower()).first():
            raise ValidationError('Email already registered.')

    def validate_username(self, field):
        if User.query.filter_by(username=field.data).first():
            raise ValidationError('Username already in use.')

Views and to_json()

As we are no longer returning templates, the responses will have to be given as JSON instead. We can provide a User class method to convert our entry to JSON with all the relevant information needed. I have also included the Stream User Token, which is removed in the endpoint if the user requested isn't the current user to prevent abuse. As a nice feature in newer versions of Flask (1.1.0 or higher), dictionaries are automatically JSON-ified when returned.

#...

class User(db.Model, UserMixin):
  #...
  
  def to_json(self):
    json_user = {
        "id": self.id,
        "name": self.name,
        "email": self.email,
        "about_me": self.about_me,
        "username": self.username,
        "image": self.gravatar(),
        "stream_token": self.stream_user_token()
    }

    return json_user
  
#...

After, we can create our registration and confirmation endpoints (in 'app/auth/views.py')

from flask_login import login_user, login_required, \
    current_user
from flask import redirect, url_for, flash
from . import auth
from .forms import RegistrationForm
from .. import db
from ..models import User
from ..email import send_email


@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        user = User(email=form.email.data.lower(),
                    username=form.username.data,
                    password=form.password.data)
        db.session.add(user)
        db.session.commit()

        # Generate and send confirmation email
        token = user.generate_confirmation_token()
        send_email(user.email, 'Confirm Your Account',
                   'templates/email/confirm_user', user=user, token=token)
        login_user(user)
        return {'user': user.to_json()}, 200
    else:
        return {'errors': {fieldName.title(): errorMessages for fieldName, errorMessages in form.errors.items()}}, 400


@auth.route('/confirm/<token>')
@login_required
def confirm(token):
    if current_user.confirmed:
        return redirect(url_for('main.index'))
    if current_user.confirm(token):
        # Commit changes and return flash
        db.session.commit()
        flash('You have confirmed your account. Thanks!')
    else:
        flash('The confirmation link is invalid or has expired.')
    return redirect(url_for('main.index'))

Errors

While we are returning errors from server-side form validation, we should also set up more generalized errors for incomplete/malformed requests (in 'app/auth/errors').

from flask import jsonify
from . import auth


@auth.app_errorhandler(403)
def forbidden(e):
    response = jsonify({'errors': {'Forbidden:': [e]}})
    response.status_code = 403
    return response


@auth.app_errorhandler(404)
def page_not_found(e):
    response = jsonify({'errors': {'Page Not Found': [e]}})
    response.status_code = 404
    return response


@auth.app_errorhandler(500)
def internal_server_error(e):
    response = jsonify({'errors': {'Internal Server Error': [e]}})
    response.status_code = 500
    return response

Flash Not Found

Using React instead of returning individual templates for each view creates new kinds of issues in trying to display flash messages. Luckily, there is a convenient Python library to handle this exact problem, Flask-Toastr. Instead of displaying as a flashed message, it displays as a neatly-styled toast, which is essentially an ephemeral notification. We can install this library in our highest project-level directory with 'pip install flask-toastr'. After, we import it as an extension (in 'app/extensions.py').

#...
from flask_toastr import Toastr
#...

toastr = Toastr()

The next step is to initialize it in the main app folder, integrating our newly created auth blueprint (in 'app/init.py').

#...
from .extensions import db, login_manager, toastr
#...

def create_app(config_name):
  #...
  toastr.init_app(app)
  
  #...
  from .auth import auth as auth_blueprint
  app.register_blueprint(auth_blueprint, url_prefix='/auth')
  
  return app

Finally, we include it in our index.html file (in 'app/static/index.html).

<!— index.html —>
<html>
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    
    <title>Offbrand React/Redux And Stream Tutorial</title>

    {{ toastr.include_jquery() }}
    {{ toastr.include_toastr_css() }}
    {{ toastr.message() }}
  </head>
  <body>
    <div id="root" />
    <script src="dist/bundle.js" type="text/javascript"></script>
    
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" 
    integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" 
    crossorigin="anonymous"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js" 
    integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ" 
    crossorigin="anonymous"></script>

    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js" 
    integrity="sha384-uefMccjFJAIv6A+rW+L4AHf99KvxDjWSu1z9VI8SKNVmz4sk7buKt/6v9KI65qnm" 
    crossorigin="anonymous"></script>
  </body>
</html>

Pulled Up By Your Bootstrap

In the previous entry, I have also imported Bootstrap 4 to style our components. There are some conflicts in how Bootstrap interacts with React, but in our case, we take a bit of a shortcut. Using a much more complete library like Reactstrap would be outside the scope of this series. However, I definitely recommend checking it out when you get the chance!

Email Confirmation

As you likely noticed, we created a confirmation functionality similar to what we used in the previous version of Offbrand. That means that we have to send a confirmation email to the user to verify their email address. As with last time, we need to create an HTML template (in 'app/static/templates/email/change_email.html').

<p>Dear {{ user.username }},</p>
<p>Welcome to <b>Offbrand</b>!</p>
<p>To confirm your account please <a href="{{ url_for('auth.confirm', token=token, _external=True) }}">click here</a>.</p>
<p>Alternatively, you can paste the following link in your browser's address bar:</p>
<p>{{ url_for('auth.confirm', token=token, _external=True) }}</p>
<p>Sincerely,</p>
<p>The Offbrand Team</p>
<p><small>Note: replies to this email address are not monitored.</small></p>

For those users whose email won't accept HTML, we give a text-based variant as well (in 'app/static/templates/email/change_email.txt').

Dear {{ user.username }},

Welcome to Offbrand!

To confirm your account please click on the following link:

{{ url_for('auth.confirm', token=token, _external=True) }}

Sincerely,

The Offbrand Team

Note: replies to this email address are not monitored.

Current User

Persisting the session on the front end, we also need to provide an endpoint for a currently authenticated user who has been issued a cookie to retrieve their information (in 'app/main/views.py').

from flask import render_template, jsonify
from flask_login import login_required, current_user
from . import main


@main.route('/')
def index():
    return render_template("index.html")


@main.route('/user', methods=['GET'])
@login_required
def get_current_user():
    return jsonify({'user': current_user.to_json()})

Sanity Check

We are finished creating the registration flow for both front and backends of our application. We have also created a thorough primer on React-Redux and handling requests. We can test the app is working by navigating to the static directory with the command line and running 'npm run watch', before opening another CLI to the main Flask app and using 'flask run'.

Final Thoughts

We have covered a lot of the overhead that comes along with making React/Redux applications, as well as started to create the authentication flow with a styling system. In the next few weeks, we are going to dive a lot deeper into creating the components of the site. Next week, we will cover the rest of authentication and user settings, as well as user profiles.

As always, thanks for reading, and happy hacking!

Note: The next post in this series can be found here

Integrating Video With Your App?
We've built a Video and Audio solution just for you. Check out our APIs and SDKs.
Learn more ->