React Grid with MySQL

Binding Smart.Scheduler with MySQL and React JS

This tutorial will show you how to create a project with React and SmartUI that supports CRUD operations over Smart.Scheduler and bind it to MySQL so you can persist your data. To connect the client side with the database we will have a NodeJS server written with ExpressJS.

Setup workspace

To share data between the database and the client (ReactJS), we need to have a connector for them. This will be a server built in NodeJS with ExpressJS.

Note that you should have MySQL installed. You can download it from here: MySQL

After installing MySQL run this script to create a database with name 'scheduler_events'

CREATE DATABASE scheduler_events;
USE scheduler_events;

CREATE TABLE Events (
    id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
    label NVARCHAR(30) NOT NULL,
    dateStart DATETIME NOT NULL,
    dateEnd DATETIME NOT NULL,
    allDay BOOL,
    description NVARCHAR(300),
    status NVARCHAR(20),
    backgroundColor NVARCHAR(7)
);

INSERT INTO Events (label, dateStart, dateEnd, allDay, description, status, backgroundColor)
VALUES ('Interview Job Candidaties', '2022-12-13 09:30', '2022-12-13 09:30', true, 'Interview different candidates for the job', 'Busy', '#e82315'), 
    ('Meet With Customers', '2022-12-14 10:30', '2022-12-14 12:00', false, 'Get to know our customers', 'Out of Office', '#34c740'), 
    ('Teach Junior Employees', '2022-12-15 15:30', '2022-12-15 17:30', false, 'Teach our juniors so that they become code masters', 'Free', '#e627bf'), 
    ('Install New Database', '2022-12-16 14:30', '2022-12-16 15:00', false, 'We will install better Database', 'Free', '#11d6f5'), 
    ('Maintain Database', '2022-12-16 15:30', '2022-12-16 17:30', false, 'Setup the newly installed Database', 'Free', '#ffbb00')

Our project will be held in two folders: server and client.

NodeJS Server

First, open a terminal in the server folder and run npm init --y. This will create a project and allows us to install packages.

Since we have a project, we need to create the main file server.js and install express and nodemon.

Express, to easily create a server and nodemon to have hot reloading.

Our server.js will hold the main logic of the application.

To make request to our API we need to setup the cors policy. This can be done with the cors package. Run npm install cors to install it.

To have the server running we will write the following:

const express = require('express');

const app = express();
const port = 3001;

app.use(require('cors')())
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.listen(port, () => {

    console.log(`App is listening on port: ${port}`);

});

The server will be running on port 3001 because our React application will run on port 3000

Now our server runs but it does nothing.

To connect it with MySQL we must download the mysql package

After installing it we will create a src folder in which we will have our router and a module for communicating with the database.

Create a file in the src called database.js and modify the connection's configuration.

database.js
const mysql = require('mysql');

let connection;

exports.createConnection = (autoConnect = true) => new Promise((resolve, reject) => {

    try {

        connection = mysql.createConnection({
            host: 'localhost',
            user: 'root',
            password: 'MySecretPass10',
            database: 'react_nodejs_products'
        });

        if (autoConnect) {
            this.connect()
                .then(() => resolve(connection))
                .catch(console.log)
        } else {
            resolve(connection);
        }


    } catch (error) {

        reject(error.message);

    }
})

exports.connect = () => new Promise((resolve, reject) => {

    connection.connect((error) => {

        if (error) {
            reject(error.message);
        }

        resolve(true);

    });

});

exports.query = (query, values) => {

    return new Promise((resolve, reject) => {

        connection.query(query, values, function (error, results) {

            if (error) {
                reject(error.message);
            }

            resolve(results);

        });

    });

}

After creating our database abstraction, we are ready to make our routes. To do this, create a file called router.js and create the following endpoints.

router.js
const router = require('express').Router();

const { query } = require('./database');

router.get('/events', (req, res) => {

    query('SELECT * FROM Events')
        .then(results => res.json(results))
        .catch((err) => {
            console.log(err);
            res.json([])
        })

})

router.get('/events/:id', (req, res) => {

    query(
        `
        SELECT * FROM Events
        WHERE id = '${req.params.id}'
        `
    )
        .then((record) => res.json(record))
        .catch((err) => {
            console.log(err);
            res.status(400).json({ error: `Failed to retrieve the event with id ${req.params.id}` })
        })

})

router.post('/events', (req, res) => {

    const { label, dateStart, dateEnd, allDay, description, status, backgroundColor } = req.body;
    console.log(req.body);
    query(`
        INSERT INTO Events (label, dateStart, dateEnd, allDay, description, status, backgroundColor)
        VALUES (${label ? `'${label}'` : null}, '${dateStart}', '${dateEnd}', ${allDay}, '${description || ''}', '${status || ''}', '${backgroundColor || ''}');
        `
    )
        .then((result) => res.json({ id: result.insertId, label, dateStart, dateEnd, allDay, description, status, backgroundColor }))
        .catch((err) => {
            console.log(err);
            res.status(400).json({ error: "Failed to add an event" })
        })

})

router.put('/events', (req, res) => {

    const { id, label, dateStart, dateEnd, allDay, description, status, backgroundColor } = req.body;

    query(
        `
        UPDATE Events
            SET 
            label = ${label ? `'${label}'` : null}, 
            dateStart = '${dateStart}', 
            dateEnd = '${dateEnd}',
            allDay = ${allDay ? true : false},
            description = '${description || ''}', 
            status = '${status || ''}',
            backgroundColor = '${backgroundColor || ''}'
        WHERE id = '${id}'
        `
    )
        .then(() => res.json({ id, label, dateStart, dateEnd, allDay, description, status, backgroundColor }))
        .catch((err) => {
            console.log(err);
            res.status(400).json({ error: `Failed to update the event with id ${id}` })
        })

})

router.delete('/events/:id', (req, res) => {

    query(
        `
        DELETE FROM Events
        WHERE id = '${req.params.id}'
        `
    )
        .then(() => res.send(true))
        .catch((err) => {
            console.log(err);
            res.status(400).json({ error: `Failed to delete the event with id ${req.params.id}` })
        })

})

module.exports = router

It is time to combine our modules in the server.js

server.js
const express = require('express');

const app = express();
const port = 3001;

app.use(require('cors')())
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use(require('./src/router'));

const { createConnection } = require('./src/database');

createConnection()
    .then(() => {
        app.listen(port, () => {

            console.log(`App is listening on port: ${port}`);

        });
    })
    .catch(console.log)

Our last step is to configure the start scripts in the package.json

{
    "name": "server",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "nodemon": "nodemon server.js",
        "start": "node server.js",
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "dependencies": {
        "cors": "^2.8.5",
        "express": "^4.18.2",
        "mysql": "^2.18.1",
        "nodemon": "^2.0.20"
    }
}

Now when we run npm run nodemon, we will have a hot reload

Creating our React client

We have a server that will connect the database and our React client, it is time to initialize our Smart.Scheduler

The first thing to do is to open a terminal in the client folder and run npx create-react-app smart-app

After creating the React project run cd smart-app to navigate to the project.

Install SmartUI with the following command: npm install smart-webcomponents-react

Run npm start and open the application

The first thing to do is to remove the CSS from App.css and replace it with this:

#scheduler {
    width: 90%;
    height: 650px;
}

In the App.js you have to import SmartUI's CSS with the following import:

import './App.css'; import 'smart-webcomponents-react/source/styles/smart.default.css';

We will create a few methods that will help us communicating with our REST API. To do that create a file called eventService.js and paste the following

eventService.js
export const getAll = () => fetch('http://localhost:3001/events')
    .then(res => res.json())

export const getEvent = (id) => fetch(`http://localhost:3001/events/${id}`)
    .then(res => res.json())

export const add = (event) => {

    event.dateStart = formatDate(event.dateStart);
    event.dateEnd = formatDate(event.dateEnd);
    event.status = event.status || '';
    event.description = event.description || '';
    event.backgroundColor = event.backgroundColor || '';

    return fetch('http://localhost:3001/events', {
        method: 'POST',
        headers: {
            "Content-Type": 'application/json'
        },
        body: JSON.stringify(event)
    })
        .then(res => res.json())
}

export const edit = (event) => {

    event.dateStart = formatDate(event.dateStart);
    event.dateEnd = formatDate(event.dateEnd);
    event.status = event.status || '';
    event.description = event.description || '';
    event.backgroundColor = event.backgroundColor || '';

    return fetch('http://localhost:3001/events', {
        method: 'PUT',
        headers: {
            "Content-Type": 'application/json'
        },
        body: JSON.stringify(event)
    })
        .then(res => res.json())
}

export const remove = (id) => fetch(`http://localhost:3001/events/${id}`, {
    method: 'DELETE'
})
    .then(res => res.json())

const formatDate = (date) => {

    const year = date.getFullYear();
    const month = (date.getMonth() + 1).toString().padStart(2, "0");
    const day = date.getDate().toString().padStart(2, "0");
    const hours = date.getHours().toString().padStart(2, "0");
    const minutes = date.getMinutes().toString().padStart(2, "0");

    let formattedDate = `${year}-${month}-${day} ${hours}:${minutes}`;

    return formattedDate;
}

Now it is time to add our Scheduler with its surrounding elements.

Place this in App.js

App.js
import './App.css';
import 'smart-webcomponents-react/source/styles/smart.default.css';

import { useEffect, useState, useRef } from 'react';

import Scheduler from 'smart-webcomponents-react/scheduler';
import { add, edit, remove, getAll } from './eventService';

function App() {

    const [events, setEvents] = useState([]);

    const schedulerRef = useRef(null);

    useEffect(() => {
        getAll()
            .then(events => {

                events = events.map(event => {

                    event.allDay = event.allDay ? true : false;
                    event.dateStart = new Date(event.dateStart);
                    event.dateEnd = new Date(event.dateEnd);

                    return event
                })

                setEvents(events)

            })
            .catch(console.log)
        }
        , []
    )

    const handleItemChange = async (e) => {

    const eventType = e.detail.type;
    const event = e.detail.item;

    switch (eventType) {
        case 'insert':
            addEvent(event)
            break;
        case 'remove':
            removeEvent(event?.id)
            break;
        case 'drag':
        case 'resize':
        case 'update':
            updateEvent(event)
            break;
        default:
            break;
    }
    }

    const addEvent = (event) => add(event)
        .then(event => {

            event.allDay = event.allDay ? true : false;
            event.dateStart = new Date(event.dateStart);
            event.dateEnd = new Date(event.dateEnd);

            setEvents(events => [...events, event])

        })
        .catch(console.log)

    const updateEvent = (event) => edit(event)
        .catch(console.log)

    const removeEvent = (id) => remove(id)
        .catch(console.log)

    const handleEditDialogOpen = (event) => {

        const editors = event.detail.editors;

        if (!editors) {
            return;
        }

        editors.repeat.classList.add('smart-hidden');
        editors.notifications.classList.add('smart-hidden');
        editors.conference.classList.add('smart-hidden');

        editors.description
            .querySelector('.smart-element')
            .placeholder = 'Enter a description for the event..';

    }

    return (
        <>
            <Scheduler
                id="scheduler"
                ref={schedulerRef}
                dataSource={events}
                view="week"
                onItemChange={handleItemChange}
                onEditDialogOpen={handleEditDialogOpen}
            />
        </>
    );
}

export default App;

Everything is ready and you data will persist after every modification that you make.