React Grid with MongoDB

Binding Smart.Scheduler with MongoDB and React JS

This tutorial will show you how to create a project with React and SmartUI that saves the events of the Smart.Scheduler in MongoDB. 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 MongoDB community server installed and MongoDB Compass if you want to have a GUI. You can download them from here: MongoDB

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.

We will connect MongoDB with the mongoose package. It is a library that allows us to easily integrate MongoDB in our application.

After installing it, we will create a src folder in which we will have our router and a our Event model.

Create a file in the src called Event.js in which we will define our model.

Event.js
const mongoose = require('mongoose');

const eventSchema = new mongoose.Schema({
    label: {
        type: String,
        required: true
    },
    dateStart: {
        type: Date,
        required: true
    },
    dateEnd: {
        type: Date,
        required: true
    },
    allDay: Boolean,
    description: String,
    status: String,
    backgroundColor: String
});

const Event = mongoose.model('Event', eventSchema);

module.exports = Event;

After creating our model, 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 Event = require('./Event');

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

    try {

        const events = await Event.find({}).lean();
        res.json(events);

    } catch (err) {

        console.log(err);
        res.json([]);

    }

})

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

    try {

        const event = Event.findById(req.params.id).lean();
        res.json(event);

    } catch (err) {

        console.log(err);
        res.json(`Cannot find event with id: ${req.params.id}`);

    }

})

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

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

    try {

        const event = await Event.create({
            label: label || '',
            dateStart,
            dateEnd,
            allDay,
            description: description || '',
            status: status || '',
            backgroundColor: backgroundColor || ''
        })

        res.json({ id: event._id, label, dateStart, dateEnd, allDay, description, status, backgroundColor })

    } catch (error) {

        console.log(err);
        res.status(400).json({ error: "Failed to add an event" })

    }

})

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

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

    try {
        
        const ev = await Event.findByIdAndUpdate(
            _id,
            {
                label: label || '',
                dateStart,
                dateEnd,
                allDay: allDay || false,
                description: description || '',
                status: status || '',
                backgroundColor: backgroundColor || ''
            },
            {
                runValidators: true
            }
        )
        console.log(ev);
        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', async (req, res) => {

    try {

        await Event.findByIdAndDelete(req.params.id)
        res.json(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 mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/smart-scheduler')
    .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",
        "mongoose": "^6.8.0",
        "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.status = event.status || '';
    event.description = event.description || '';
    event.backgroundColor = event.backgroundColor || '';
    event.allDat = event.allDay || false;

    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.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())

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 => 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 => 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. Now you are able to create, update or delete events.