CreateIT
CreateIT
BLOG

Chat with datepicker

Ninja behind smartphone and calendar

Chat with datepicker

SHARE

Challenge:
allow a chat member to pick a date using datepicker and send a response
Solution:
use the react-datepicker package and the SendMessageWithSendBird component

Nowadays, chatbots are getting more and more popular. Bots follow sets of rules and scenarios to quickly provide information to Customers, including discounts, shipping costs or refunds. Instead of hiring additional Customer Service members, ChatBots can handle most of the frequently asked questions.

Of course, bots are not perfect and Customers are confronted with many issues. For example, a question asked in plain language may not be properly processed and we can get a response: “Sorry, I don’t understand. Could you rephrase your question”.

Picking the date in the chat

Regarding User Experience, chats are getting better. They offer predefined options to pick from, or introduce Machine Learning to develop understanding of questions asked in the natural language.

What about asking a Chat Member about picking a date for scheduling package delivery or booking a meeting? How could we handle this? We can ask a user to send a preferred date via a Chat Message, but this can be sent in any format, like: 29 June 2022, 2022/06/29, 29-6-2022, 29.06.2022, June-29 etc. This is not the perfect solution for storing a date in a database.

In this tutorial we’re going to add a datepicker to chat and allow a chat member to pick a preferred date from the calendar view. The user will see months and days of the week. After choice confirmation, the date in the predefined format will be sent to the Chat.

Calendar inside a chat window

Datepicker with SendBird

SendBird offers messaging-as-a-service. Founded in 2013, it provides the chat API and the backend for apps enabling real-time communication. You can build your own chat on top of SendBird API. This example is a real React application built with SendBird, and it includes Datepicker component implementation.

React-datepicker

React-datepicker NPM package seems like a good choice, with over 1 million downloads weekly, it is one of the most popular datepicker packages on the market. Another benefit of using it is the extended Demo page with many usage examples: https://www.npmjs.com/package/react-datepicker

After adding a package to package.json we can activate the datepicker with a couple of lines of code:

<DatePicker
    selected={startDate}
    onChange={(date) => setStartDate(date)}
    inline
/>

Channel renderCustomMessage

When customizing the SendBird chat, a clever solution is to use the renderCustomMessage method. By doing it, all original styling and functionality will stay the same, only a particular type of message render will be overwritten. In our situation, we’re going to keep all other type of messages without change. Only the datepicker will have custom rendering.

<Channel
    channelUrl={currentChannelUrl}
    renderCustomMessage={(message, channel) => {
      if (message.customType === 'datepicker' || message.message === 'datepicker') {
        return (
            displayDatePicker(setChoiceCompleted, setMsgToSent, message)
        )
      }
    }}
/>

It’s worth to mention that renderCustomMessage can’t have a React Component as the returned value, so we need to use a normal function as a wrapper for adding Datepicker:

export const displayDatePicker = (setChoiceCompleted, setMsgToSent, message) => {
    return () => (
        <RenderDatepicker setChoiceCompleted={setChoiceCompleted} setMsgToSent={setMsgToSent} message={message} />
    );
}

Javascript date formatting

We have multiple options for sending a date in a message. Date object provides different methods to display the full date. Here are examples:

const today = new Date();
today.toDateString(); // Tue Mar 01 2022
today.toISOString(); // 2022-03-01T13:20:46.743Z
today.toUTCString(); // Tue, 01 Mar 2022 13:20:46 GMT
today.toLocaleDateString(); // 1.03.2022

date-fns library

date-fns, the npm library for manipulating javascript dates, called itself  ‘lodash for dates’. We will use it only for one purpose – to display the formatted date as button text, for example: I’m picking 12-01-2022.

Storing choice in localStorage

Once user picked the date – will be nice to remember his choice. We’re using browser localStorage to save the date object, the key value is the unique messageID. Once the “I’m picking date” button is clicked, CSS class is applied and the date can’t be picked anymore. Also, after browser refresh, we will see the saved date in the calendar view.

Example data stored in the browser (localStorage):

( message.messageID → pickedDate.toISOString() )

1653910041 → 2022-03-27T13:24:19.000Z

Chat with developer console opened

Trigger datepicker

This tutorial focuses on the frontend part – chat in React, but we need to somehow trigger the displaying of datepicker to the user. In our example, we can type the message: datepicker, and our function will be forced to render.

if (message.customType === 'datepicker' || message.message === 'datepicker') {
        return (
            displayDatePicker(setChoiceCompleted, setMsgToSent, message)
        )
}

We will also have backend part in the production application. The backend application will use webhooks to receive messages from the chat, and the API Platform to generate responses. For example: backend will prepare a message with message.customType, called datepicker, and this will be a signal for the React Chat to render a calendar with the option to pick a date.

SendBird UIKit – datepicker in the chat

Here is the full Demo. In our example, Datepicker will be rendered as an inline element. The chat member needs to complete 2 steps: first, select the preferred date, and second, click the button to confirm the choice. That will ensure that the user selected the date on purpose, and hasn’t clicked it by mistake.

chat window with calendar inside

We’re using the SendBird UIKit for React with one customization: renderCustomMessage will detect that the message custom type is ‘datepicker’ and render the calendar for picking a day. Another component called withSendBird is responsible for sending the final date as a chat message.

The first step is to install dependencies:

npm install react-datepicker
npm install date-fns
npm install sendbird-uikit

The Main app displays Channel List. Setting DEFAULT_CHANNEL_URL as active one.

// App.jsx
import React, {useState, useEffect, useCallback} from 'react';
import {
    SendBirdProvider,
    Channel,
    ChannelList
} from 'sendbird-uikit';
import "sendbird-uikit/dist/index.css";
import './App.css';
import {displayDatePicker} from "./RenderDatepicker";
import SendMessageWithSendBird from "./SendMessage";
/**
 * Configuration
 */
const APP_ID = "AAA";
const USER_ID = "BBB";
const DEFAULT_CHANNEL_URL = "CCC";
export default function App() {
    const channelSort = useCallback((channels) => { // useCallback to memoize the fn
        if (channels.length === 0) {
            return channels;
        }
        const channel = channels.find(c => c.url === DEFAULT_CHANNEL_URL);
        if (!channel) {
            return channels;
        }
        const otherChannels = channels.filter(c => c.url !== channel.url);
        otherChannels.sort(function (a, b) {
            if (a.name < b.name) {
                return -1;
            }
            if (a.name > b.name) {
                return 1;
            }
            return 0;
        });
        return [channel, ...otherChannels];
    }, []);
    const [msgToSent, setMsgToSent] = useState({
        msg: "",
        msgType: "",
        isSilent: false,
        extraData: "",
        readyToSend: false
    });
    const [choiceCompleted, setChoiceCompleted] = useState(false);
    const [currentChannelUrl, setCurrentChannelUrl] = useState("");

    useEffect(() => {
        return () => {
            /**
             * reset state
             */
            setMsgToSent({...msgToSent, readyToSend: false});
            setChoiceCompleted(false);
        };
        // eslint-disable-next-line
    }, [choiceCompleted]);
    useEffect(() => {
        return () => {
            setChoiceCompleted(true);
        };
        // eslint-disable-next-line
    }, [msgToSent]);
    const component = (!choiceCompleted && msgToSent.readyToSend) ? (
        <SendMessageWithSendBird extraData={msgToSent.extraData} msg={msgToSent.msg} isSilent={msgToSent.isSilent}
                                 customType={msgToSent.msgType} channelUrl={currentChannelUrl}/>) : (<></>);
    return (
        <div className="App">
            <SendBirdProvider appId={APP_ID} userId={USER_ID} theme={'light'}>
                <div style={{display: 'flex',  flexDirection: 'column', height: '100%'}}>
                    <ChannelList
                        sortChannelList={channelSort}
                        onChannelSelect={(channel) => {
                            if (channel && channel.url) {
                                setCurrentChannelUrl(channel.url);
                            }
                        }}
                    />
                    <Channel
                        channelUrl={currentChannelUrl}
                        renderCustomMessage={(message, channel) => {
                            if (message.customType === 'datepicker' || message.message === 'datepicker') {
                                return (
                                    displayDatePicker(setChoiceCompleted, setMsgToSent, message)
                                )
                            }
                        }}
                    />
                </div>
                {component}
            </SendBirdProvider>
        </div>
    );
}

Our datepicker component renders 2 steps: a calendar for picking a date and a confirm button.

// src/RenderDatepicker.jsx
import React, {useState} from 'react';
import { format } from 'date-fns';
import DatePicker from 'react-datepicker';
import "react-datepicker/dist/react-datepicker.css";
export const RenderDatepicker = (props) => {
    const {setChoiceCompleted, setMsgToSent, message} = props;
    const localStorageDate = localStorage.getItem(message.messageId);
    const initialDate = localStorageDate ? new Date(localStorageDate) : new Date();
    const [startDate, setStartDate] = useState(initialDate);
    const handleConfirm = () => {
        setChoiceCompleted(false);
        let extraData = {
            choices: [startDate],
        };
        const fullDate  = startDate.toISOString();
        const formattedDate  = startDate.toDateString();
        localStorage.setItem(message.messageId, fullDate);
        const extraData2 = JSON.stringify(extraData);
        const msg = JSON.stringify(formattedDate);
        setMsgToSent({msg: msg,msgType: "date", isSilent: true, extraData: extraData2, readyToSend: true});
    }
    return (
        <div className={(localStorageDate) ? 'is-already-taken' : ''}>
            <p>1. Pick date:</p>
            <DatePicker
                selected={startDate}
                onChange={(date) => setStartDate(date)}
                inline
            />
            <p>2. and confirm:</p>
            <span>
                <button className={'myButton mb1'} onClick={() => handleConfirm()} >{"I'm picking " + format(startDate, "dd-MM-yyyy")} {(localStorageDate) ? "✓" : "⮞" }</button>
            </span>
        </div>
    );
};
export const displayDatePicker = (setChoiceCompleted, setMsgToSent, message) => {
    return () => (
        <RenderDatepicker setChoiceCompleted={setChoiceCompleted} setMsgToSent={setMsgToSent} message={message} />
    );
}

And finally styles for making app 100% window height, button styling and “disabling” the datepicker once the confirm button is clicked (is-already-taken CSS class):

.App {
  text-align: center;
}
html, body, #root, .App {
  height:100%;
}
.myButton{
  font-size:15px;
  width:auto;
  padding:10px 25px;
  border-width:1px;
  color:#fff;
  border-color:#d02718;
  font-weight:normal;
  border-radius: 6px;
  box-shadow: 3px 4px 0px 0px #8a2a21;
  background:linear-gradient(#c62d1f, #f24437);
  margin:20px 0;
}
.myButton:hover {
  background: linear-gradient(#f24437, #c62d1f);
}
.is-already-taken {
  opacity:0.7;
  pointer-events: none;
}

App configuration

Make sure to fill in the config data. APP_ID is the application identifier from the SendBird panel. USER_ID is the chat member ID number, and the DEFAULT_CHANNEL_URL is the full channel url we would like to display, for example: sendbird_group_channel_XXXXXX_YYYYYYYYYY

// App.jsx
/**
 * Configuration
 */
const APP_ID = "AAA";
const USER_ID = "BBB";
const DEFAULT_CHANNEL_URL = "CCC";

Chat window opened in a browser

Datepicker options

The “React Date Picker” Plugin offers multiple configuration options: we can display time, customize the look of most of the elements, add localization for our language, add a custom CSS class, use date range to select days between 2 dates, filter dates or times, add a dropdown for months and many more. The available configuration options for reactdatepicker can be found in the official documentation: https://reactdatepicker.com/

Adding timepicker

At this point, the addition of a timepicker (the ability to pick the exact hour of the meeting) is easy to implement. We only need to add a couple of modifications to our code. First, the format of the formattedDate variable, which is used for sending the message, needs to be changed to include time. Second, the modification is to add options to datepicker to render a list of hours to pick from.

It’s worth to mention that the toISOString() method returns a string in a simplified extended ISO format (ISO 8601), which is always 24 or 27 characters long (YYYY-MM-DDTHH:mm:ss.sssZ or ±YYYYYY-MM-DDTHH:mm:ss.sssZ, respectively). The timezone is always zero UTC offset, as denoted by the suffix “Z”.

Example:
Picked date: March 30, 2022 7:15 PM
Message sent: 2022-03-30T17:15:00.000Z

And that’s a totally correct value, because in Poland we have the UTC+2h timezone ( from 2022-03-27 to 2022-10-30 ). To sum up, whatever timezone the user is using, we will get a normalized time that follows UTC time.

// src/RenderDatepicker.jsx
// adding timepicker to datepicker
// SendBird UIKit - datepicker in the chat
export const RenderDatepicker = (props) => {
    const handleConfirm = () => {
        (...)
        // send message using full format (with time)
        const formattedDate  = startDate.toISOString();
        (...)
    }
    return (
        <div className={(localStorageDate) ? 'is-already-taken' : ''}>
            <p>1. Pick date:</p>
            <DatePicker
                selected={startDate}
                onChange={(date) => setStartDate(date)}
                inline
                showTimeSelect
                timeFormat="HH:mm"
                timeIntervals={15}
                timeCaption="time"
                dateFormat="MMMM d, yyyy h:mm aa"
            />
            <p>2. and confirm:</p>
            <span>
                <button className={'myButton mb1'} onClick={() => handleConfirm()} >{"I'm picking " + format(startDate, "MMMM d, yyyy h:mm aa")} {(localStorageDate) ? "✓" : "⮞" }</button>
            </span>
        </div>
    );
};

Chat window with calendar

That’s it for today’s tutorial. Make sure to follow us for other useful tips and guidelines, and don’t forget to subscribe to our newsletter.

Need help?

  • Looking for support from experienced programmers?

  • Need to fix a bug in the code?

  • Want to customize your webste/application?

ADD COMMENT

Your email address will not be published.

createIT Contact