Chat with datepicker - createIT
Get a free advice now!

    Pick the topic
    Developer OutsourcingWeb developingApp developingDigital MarketingeCommerce systemseEntertainment systems

    Thank you for your message. It has been sent.

    Chat with datepicker

    April 20, 2022
    Last update: April 4, 2024
    7 min read
    16
    0
    0
    Chat with datepicker

    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.

    Animation of a calendar

    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

    Configuration panels

    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.

    Calendar image in chat window on white background with text

    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";
    Calendar inside chat animation

    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>
        );
    };
    Calendar animation

    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.

    Support – Tips and Tricks
    All tips in one place, and the database keeps growing. Stay up to date and optimize your work!

    Contact us