React, Flask-restful and SQLAlchemy – getting data from the database

Frontend:

I download the data from the backend when selecting the option from the Navbar component of Bulma framework. I use the useEffect hook to get the data to be read during component rendering. Then, a query to the backend is created, and a token is passed as a parameter, set when the user logs in. The response is sent from the backend in the form of a Promise object, from which if the query is completed successfully, the cursor variable is set (change of state with setCursor). Cursor and setCursor function are passed to the component from the parent App.js component as parameters (props), i.e.

import LoginForm from '../Forms/LoginForm/LoginForm'
import { useEffect } from "react";

function Contracts({token, putToken, cursor, setCursor}) {

    useEffect( 
        () => {
            const token = sessionStorage.getItem('token')
            fetch('/api/contract/' + token)
            .then(res => res.json()).then(data => setCursor(data)).catch((err) => console.log(err))
        }, [token, cursor],
    );
    
    if (!token) {
        return <LoginForm putToken={putToken} />
    };

In order for the component to refresh the data after successfully logging in, in the additional parameter of the useEffect method in the array, I put the token variable, which is set in the LoginForm.js component.

In order for the component to refresh the data after changing the cursor content, e.g. by adding a new contract, I have placed the cursor variable as an additional parameter of the useEffect method.

Individual data lines are placed in the table, i.e.

<tbody>
    {cursor && Object.keys(cursor).map((keyName, keyIndex) => 
    <tr key={keyIndex}>
            <td>{keyIndex+1}</td>
            <td>{(cursor[keyName].status)}</td>
            <td>{(cursor[keyName].contract_number)}</td>
            <td>{(cursor[keyName].contractor)}</td>
            <td>{(cursor[keyName].customer)}</td>
            <td>{(cursor[keyName].date_of_order)}</td>
            <td>{(cursor[keyName].date_of_delivery)}</td>
            <td>{(cursor[keyName].pallets_position)}</td>
            <td>{(cursor[keyName].pallets_planned)}</td>
            <td>{(cursor[keyName].pallets_actual)}</td>
            <td>{(cursor[keyName].warehouse)}</td>
     </tr>)}   
</tbody>

Backend with Flask-restful:

In the init.py file, I add another resource class:

from api.resources.contracts import AllContracts

api.add_resource(AllContracts, '/api/contract/<token>', endpoint='all_contracts')

In contracts.py, I define the resource class:

from datetime import datetime
from flask import request
from flask_restful import Resource
from .. import db
from ..common.models import User, Contract

class AllContracts(Resource):
    def get(self, token):
        token = token.strip('"')
        user = User.query.filter_by(token=token).first()
        contracts = Contract.query.filter_by(customer=user.username).order_by(Contract.id.desc()).all()
        cursor = {}
        for i, contract in enumerate(contracts):
            cursor[i] = contract.serialize()
        return cursor

In the models.py file I define, among others, how the Contract class inheriting from the Model class from SQLAlchemy is to be serialized:

from .. import db
from datetime import datetime

class Contract(db.Model):
    '''Model of contract between a contractor and a customer'''

    id = db.Column(db.Integer, primary_key=True)
    status = db.Column(db.String(10), nullable=False, default='open')
    contract_number = db.Column(db.String(20), nullable=False)
    contractor = db.Column(db.String(20), db.ForeignKey('user.username'), nullable=False)
    customer = db.Column(db.String(20), db.ForeignKey('user.username'), nullable=False)
    date_of_order = db.Column(db.Date, nullable=False, default=datetime.utcnow)
    date_of_delivery = db.Column(db.Date, nullable=False)
    pallets_position = db.Column(db.Integer)
    pallets_planned = db.Column(db.Integer, nullable=False)
    pallets_actual = db.Column(db.Integer)    
    warehouse = db.Column(db.String(10), nullable=False)

    def serialize(self):
        return {'id': self.id,
                'status': self.status,
                'contract_number': self.contract_number,
                'contractor': self.contractor,
                'customer': self.customer,
                'date_of_order': datetime.strftime(self.date_of_order, '%Y-%m-%d'),
                'date_of_delivery': datetime.strftime(self.date_of_delivery, '%Y-%m-%d'),
                'pallets_position': self.pallets_position,
                'pallets_planned': self.pallets_planned,
                'pallets_actual': self.pallets_actual,
                'warehouse': self.warehouse 
                }

React and Bulma navbar

Problems solved:

  1. How to collapse a burger menu when selecting options from the menu (by default, selecting options from the burger menu leaves the menu expanded).
  2. How to collapse a pull-down menu when selecting an option. (by default, the drop-down menu does not close when an option is selected)

re. 1

The menu finally collapses when an option is selected.
<div id="navbarBasicExample" className={`navbar-menu ${burgerActive? "is-active": ""}`}>
                <div class="navbar-start">
                <Link to="/" class="navbar-item" onClick={handleOnClick}>
                    Home
                </Link>

wherein:

    const [burgerActive, setBurgerActive] = useState(false)

    const handleOnClick = () => {
        setBurgerActive(false)
    }

re. 2

The pull-down menu collapses when an option is selected.
<div class="navbar-item has-dropdown is-hoverable" key={location.pathname}>

wherein:

import {
    Link, useLocation
  } from 'react-router-dom' 

const Navbar = () => {
    let location = useLocation();

Bulma navbar and React

Navbar as a React.js component.

Added support for the hamburger button.

Navbar.js file:

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

const Navbar = () => {
    const [burgerActive, setBurgerActive] = useState(false)
    return (
        <nav class="navbar" role="navigation" aria-label="main navigation">
            <div class="navbar-brand">
                <a class="navbar-item" href="https://slawomirkwiatkowski.pl">
                <div class="title">Contracts</div>
                </a>

                <a role="button" className={`navbar-burger ${burgerActive? "is-active": ""}`} 
                    aria-label="menu" aria-expanded="false" 
                    data-target="navbarBasicExample"
                    onClick={() => setBurgerActive(!burgerActive)}
                >
                <span aria-hidden="true"></span>
                <span aria-hidden="true"></span>
                <span aria-hidden="true"></span>
                </a>
            </div>

            <div id="navbarBasicExample" className={`navbar-menu ${burgerActive? "is-active": ""}`}>
                <div class="navbar-start">
                <Link to="/" class="navbar-item">
                    Home
                </Link>

                <a class="navbar-item">
                    Documentation
                </a>

                <div class="navbar-item has-dropdown is-hoverable">
                    <a class="navbar-link">
                    More
                    </a>

                    <div class="navbar-dropdown">
                    <Link to="/about" class="navbar-item">
                        About
                    </Link>
                    <a class="navbar-item">
                        Jobs
                    </a>
                    <a class="navbar-item">
                        Contact
                    </a>
                    <hr class="navbar-divider"/>
                    <a class="navbar-item">
                        Report an issue
                    </a>
                    </div>
                </div>
                </div>

                <div class="navbar-end">
                <div class="navbar-item">
                    <div class="buttons">
                    <Link to="/user/register" class="button is-link">
                        <strong>Sign up</strong>
                    </Link>
                    <Link to="/user/login" class="button is-light">
                        Sign In
                    </Link>
                    </div>
                </div>
                </div>
            </div>
        </nav>
    )
}

export default Navbar

React & Flask # 1 – user registration

Frontend:

import React, {useState} from 'react'
import { useHistory } from "react-router-dom";
import {useForm} from "react-hook-form";
import './RegisterForm.css'
function RegisterForm() {

    let history = useHistory();

    const {register, handleSubmit, getValues, formState: { errors } } = useForm();

    const [userRole, setUserRole] = useState('')

    const onSubmit = (data, e) => {
        e.preventDefault()
        data['role'] = userRole
        fetch('/api/user/register', {
            method: 'POST',
            headers: {
                'Content-type': 'application/json',
            },
            body: JSON.stringify(data)
        })
        .then(res => console.log(res.json()))
        history.push('/user/login')
    }
    return (
        <div className="columns">
            <div className="column is-narrow-desktop is-offset-5">
                <div className="card is-3 mt-5">
                    <header className="card-header">
                        <p className="card-header-title has-text-primary-light has-background-dark">
                            User Registration
                        </p>
                    </header>
                    <div className="card-content">
                        <div className="content">
                            <form onSubmit={handleSubmit(onSubmit)}>
                                <div className="field">
                                    <div className="control">
                                        <select name="role" required
                                            value={userRole}
                                            onChange={(e) => setUserRole(e.target.value)}>
                                            <option value="" disabled defaultValue="" hidden>Role</option>
                                            <option value="Contractor" >Contractor</option>
                                            <option value="Customer">Customer</option>
                                        </select>
                                    </div>
                                </div>
                                <div className="field control">
                                    <input placeholder="Username" name="username" 
                                        {...register("username", {required: true, minLength: 3})} />
                                    <p className="help is-danger">
                                        {errors.username?.type === 'required' && "This field is required"}
                                        {errors.username?.type === 'minLength' && "This field must contain at least 3 characters"}
                                    </p>
                                </div>
                                <div className="field control">
                                    <input placeholder="Email" id="email"
                                        autoComplete="email"
                                        {...register("email", {required: true, pattern: /\S+@\S+\.\S+/})} />
                                    <p className="help is-danger">
                                        {errors.email?.type === 'required' && "This field is required"}
                                        {errors.email?.type === 'pattern' && "Invalid email address"}
                                    </p>
                                </div>
                                <div className="field control">
                                    <input type="password" placeholder="Password" id="password" name="password"
                                        autoComplete="new-password"
                                        {...register("password",  {required: true, minLength: 6})} />
                                    <p className="help is-danger">
                                        {errors.password?.type === 'required' && "This field is required"}
                                        {errors.password?.type === 'minLength' && "This field must contain at least 6 characters"}
                                    </p>
                                </div>
                                <div className="field control">
                                    <input type="password" placeholder="Retype Password" id="confirm_password"
                                        autoComplete="new-password" 
                                        {...register("confirm_password",  {required: true, minLength: 6,
                                            validate: value => value === getValues('password')})} />
                                    <p className="help is-danger">
                                        {errors.confirm_password?.type === 'required' && "This field is required"}
                                        {errors.confirm_password?.type === 'minLength' && "This field must contain at least 6 characters"}
                                        {errors.confirm_password?.type === 'validate' && "Passwords don't match"}
                                    </p>
                                </div>

                                <div className="control">
                                    <input type="submit" value="Register" className="button is-link" />    
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        

    )
}

export default RegisterForm

Backend – main file: __init__py

def create_app(Config):
    app =  Flask(__name__, static_folder='../build', static_url_path='/')
    app.config.from_object(Config)
    api.init_app(app)
    db.init_app(app)
    app.db = db
    bcrypt = Bcrypt(app)
    app.bcrypt = bcrypt


    from .common import utils
    from .common import routes
    app.register_blueprint(api_bp)
    app.register_blueprint(utils.bp)
    app.register_blueprint(routes.bp)
    return app


from api.resources.auth import UserRegister
api.add_resource(UserRegister, '/api/user/register', endpoint='user_register')

File that contains the Resource class for handling user registration:

from flask import current_app, request
from flask_restful import Resource
from api.common.parsers import parser
from ..common.utils import send_email
from ..common.models import User
from .. import db
class UserRegister(Resource):

    def post(self):
        req = request.json
        username = req['username']
        password = req['password']
        email = req['email']
        role = req['role']
        bcrypt = current_app.bcrypt
        hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
        status = send_email(email, category='confirm_account')
        user = User(username=username, password=hashed_password, email=email, role=role)
        db.session.add(user)
        db.session.commit()
        return {"message": status}

Forms in React using React Hook Form

My sample login form:

import React from 'react'
import {useForm} from "react-hook-form"


function LoginForm() {

    const {register, handleSubmit,  formState: { errors } } = useForm();
    

    const onSubmit = (data, e) =>  {
        e.preventDefault();
        console.log(data)
        };


    return (
        <form onSubmit={handleSubmit(onSubmit)}>

            <input placeholder="Enter username" id="username" 
                {...register("username", {required: true, minLength: 3})} />
            {errors.username?.type === 'required' && "This field is required"}
            {errors.username?.type === 'minLength' && "This field must contain at least 3 characters."}
            {/* {errors.username && <span>This field is required and must contain at least 3 characters.</span>} */}
            
            <input type="password" placeholder="Password" id="password" 
                {...register("password",  {required: true, minLength: 6})} />
            {errors.password?.type === 'required' && "This field is required"}
            {errors.password?.type === 'minLength' && "This field must contain at least 6 characters."}


            <input type="submit" value="Login" />    

        </form>
    )
}

export default LoginForm

 

React app router snippet

Main app component sample snippet with BrowserRouter:

import './App.css';
import {
  BrowserRouter,
  Switch,
  Route,
  Link
} from 'react-router-dom' 
import LoginForm from './components/LoginForm/LoginForm'

function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Switch>
          <Route path="/" exact>
            <LoginForm />
            <Link to="/about">About</Link>
          </Route>
          <Route path="/about">
            <Link to="/">Go to main page</Link>
          </Route>
          <Route path="/to-component" component={LoginForm} />
        </Switch>
      </div>
    </BrowserRouter>
  );
}

export default App;