Bulma – modal component + React +Flask-RESTful

In the post I will describe how to activate the modal component from the Bulma framework, so that after pressing the delete row-contract button in the contract table, it is possible to confirm or cancel the deletion of the contract.

<tbody>
  {cursor && Object.keys(cursor).map(
      (keyName, keyIndex) =>
      <tr  className="has-text-centered is-size-7"
           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>
          <td>
            <FaEdit />
            <FaTrashAlt onClick={() => 
               showModal(cursor[keyName].id)}
               title={`Delete contract ${keyIndex+1}`} />
          </td>
      </tr>)}   
</tbody>
Contract deletion

Instead of directly calling the deleteContract() function, the showModal() function is called, which gets as the id parameter of the selected contract.

The showModal() function sets the variables id and modal, respectively.

const [modal, setModal] = useState(false)
const [id, setId] = useState()

const showModal = (id) => {
        setId(id)
        setModal(!modal)
    }

The id variable stores the id number of the selected contract, while the modal variable stores the boolean value that specifies the state of the modal window (displayed or invisible-default). Calling the setModal() function changes the default value – false of the modal variable to true.

The code of the modal window displaying the confirmation of contract deletion looks like this:

<div class={`modal ${modal && 'is-active'}`}>
  <div class="modal-background"></div>
  <div class="modal-card column is-one-quarter">
     <header class="modal-card-head">
        <p class="modal-card-title has-text-danger">
           Delete Contract?
        </p>
      </header>
      <section class="modal-card-foot">
         <button class="button is-danger" 
             onClick={() => deleteContract(id)}>Delete
         </button>
         <button class="button"
             onClick={() => setModal(!modal)}>Cancel
         </button>
       </section>
  </div>
</div>

Selecting the Cancel button makes the modal window invisible. Selecting the Delete button activates the function of deleting the contract, i.e.

const deleteContract = (id) => {
  setCursor(Object.values(cursor)
    .filter((row) => row.id !== id))
  fetch('/api/contract/delete', {
    method: 'POST',
    headers: {
      'Content-type': 'application/json',
     },
       body: JSON.stringify({'id': id, 'token': token})
  })
  .then(res => res.json())
  .then(data => console.log(data))
  .catch((err) => console.log(err));
  setModal(!modal)
}

The setCursor function gets the values of the cursor object and filters them so that the selected contract is discarded. Then the api is called in Flask, in addition to the contract id, the user’s token is also sent. The result of a successful contract canceling is displayed in the console.

What the API looks like in Flask I described in the previous post.

In the last row of the deleteContract() function, I set the modal window inactive.

Delete a record (Flask-RESTful, SQLAlchemy, React, and Bulma CSS framework)

In the contract table, after pressing the appropriate button to delete a row in the table, the deleteContract function is activated, which takes the contract id value as a parameter, i.e.:

<td>
    <FaTrashAlt onClick={() => deleteContract(cursor[keyName].id)} />
</td>

The deleteContract function filters the data of the cursor variable in such a way that the row that we want to delete is rejected. Then a query is called to the backend, where the POST method sends the value of the contract id and the token. When the “deletion” of the record is successful, the API sends information in the message variable (in the given example, the value of this variable is displayed in the console).

const deleteContract = (id) => {
    setCursor(Object.values(cursor).filter((row) => row.id !== id))
    fetch('/api/contract/delete', {
            method: 'POST',
            headers: {
                'Content-type': 'application/json',
            },
            body: JSON.stringify({'id': id, 'token': token})
        })
        .then(res => res.json()).then(data => console.log(data)).catch((err) => console.log(err));
    }

Backend we Flask-RESTful z wykorzystaniem SQLAlchemy:

class DeleteContract(Resource):
    def post(self):
        req = request.json
        contract_id = req['id']
        token = req['token'].strip('"')
        user = User.query.filter_by(token=token).first()
        if user.role == 'Customer':
            contract = Contract.query.filter_by(customer=user.username).filter_by(id=contract_id).first()
            if contract:
                contract.status ='cancelled'
                db.session.commit()
                return {"message": "Row deleted"}

I assign the id and token values sent using the POST method to the corresponding variables contract_id and token. Then I find the user in the database based on the token sent. If the user belongs to the Customer group, the contract is searched based on the user name and the contract id number. If this contract exists, instead of removing it from the database, I change its status to cancelled.

I also updated the post() method of the AllContracts(Resource) class so that the created cursor object does not contain records with an cancelled status, i.e.:

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

The useEffect() hook for loading contracts after selecting the Contracts option from the menu looks like this:

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

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, setCursor],
    );
    
    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 setCursor function 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;