CreateIT
CreateIT
BLOG

Adding rating stars to chat

Ninja with an ok hand sign and a smartphone in the background

Adding rating stars to chat

SHARE

Challenge:
allow chat members to rate chat by clicking stars
Solution:
prepare a custom message type with custom rendering

Building your own chat using SendBird? Do you want to get user rating at the end of a chat conversation? This tutorial will show you how.

Typically, a Chat is a conversation between 2 users: the Client and the Agent or the Client and the Bot. It will be nice to allow the Client to rate the quality of the chat experience. We’re going to render clickable stars, which can be clicked only once. By clicking, the user will send a rate value as a response.

ChannelList with default channel

By default, channels are rendered from newest to oldest (based on channel creation time). We will use the sortChannelList parameter and place our DEFAULT_CHANNEL_URL as the first item on the list. It will also be autoloaded on page refresh ( setCurrentChannelUrl() ).

Keep messages styling

To keep messages styling included in React UIKit, we’re going to use renderCustomMessage prop that allows to change the render of only one customType, keeping other messages without change. Alternatively, we can overwrite renderChatItem, but the result will be the loss of all messages styling, removing reaction emoticons, typing indicator and the “message received” icon.

Message HOC (High Order Component) expects a string as a returned value, so instead of using Component, renderStars will be a javascript function.

Chat window with star ratings

Chat settings

Make sure to change settings to your own values. You can also control the amount of displayed star icons.

const APP_ID = "AAA";
const USER_ID = "BBB";
const DEFAULT_CHANNEL_URL = "CCC";
const TOTAL_STARS = 10;

RatingStars Message

SendBirdProvider is a wrapper component for our customizations. Final changes include: changing the order of channel list and changing the rendering of one message type. The function is expecting: message.customType to be defined as “type2”. For this condition, starts will be rendered.

// App.jsx
import React, {useState, useCallback} from 'react';
import {
  SendBirdProvider,
  Channel,
  ChannelSettings,
  ChannelList
} from 'sendbird-uikit';
import "sendbird-uikit/dist/index.css";
import './App.css';
import {renderStars} from "./renderStars";
const APP_ID = "AAA";
const USER_ID = "BBB";
const DEFAULT_CHANNEL_URL = "CCC";
const TOTAL_STARS = 10;
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 [currentChannelUrl, setCurrentChannelUrl] = useState("");
  return (
      <div className="App">
        <SendBirdProvider appId={APP_ID} userId={USER_ID} >
          <div style={{ display: 'flex', height: '100%' }}>
            <Channel
                channelUrl={currentChannelUrl}
                renderCustomMessage={(message, channel) => {
                  if (message.customType === 'type2') {
                    return (
                        renderStars(currentChannelUrl, TOTAL_STARS)
                    )
                  }
                }}
            />
            <ChannelSettings channelUrl={currentChannelUrl} />
            <ChannelList
                sortChannelList={channelSort}
                onChannelSelect={(channel) => {
                  if (channel && channel.url) {
                    setCurrentChannelUrl(channel.url);
                  }
                }}
            />
          </div>
        </SendBirdProvider>
      </div>
  );
}

Basic styling and disabling ability to rate the second time.

.App {
  font-family: sans-serif;
  text-align: center;
  height: calc(100vh - 100px);
}
.sendbird-conversation {
  max-width:50%;
}
.starsRating {
  display:block;
  margin:2px 0;
}
.star {
  display:inline-block;
  font-size:2rem;
  color: #ff9933;
  cursor: pointer;
}
.starsRating.disabled {
  pointer-events:none;
  opacity: 0.5;
}

The component for sending a message (when a Star is clicked):

// SendMessage.jsx
import React, { useEffect } from "react";
import {
    withSendBird,
    sendBirdSelectors,
} from "sendbird-uikit";
const CustomComponent = (props) => {
    const {
        msg,
        customType,
        channelUrl,
        extraData,
        sendMessage,
        updateLastMessage,
        sdk,
    } = props;
    const sendSelectTypeMessage = (msg, customType, channelUrl, extraData = null) => {
        const params = new sdk.UserMessageParams();
        if(!msg){
            return;
        }
        if(customType){
            params.customType = customType;
        }
        params.message = msg;
        if(extraData){
            params.data = extraData;
        }
        sendMessage(channelUrl, params)
            .then(message => {
            })
            .catch(e => {
                console.warn(e);
            });
    }
    useEffect(() => {
        sendSelectTypeMessage(msg, customType, channelUrl, extraData);
    });
    return(
        <></>
    );
};
const SendMessageWithSendBird = withSendBird(CustomComponent, (state) => {
    const sendMessage = sendBirdSelectors.getSendUserMessage(state);
    const updateLastMessage = sendBirdSelectors.getUpdateUserMessage(state);
    const sdk = sendBirdSelectors.getSdk(state);
    return ({
        sendMessage,
        updateLastMessage,
        sdk
    });
});
export default SendMessageWithSendBird;

And the most interesting part of the code – the main function for rendering stars, handling hover event and triggering sending a message.

// renderStars.jsx
import React, {useState, useEffect} from 'react';
import SendMessageWithSendBird from "./SendMessage";
/**
 * Stars rating
 */
const Star = ({ marked, starId }) => {
    return (
        <span data-star-id={starId} className="star" role="button">
              {marked ? '\u2605' : '\u2606'}
            </span>
    );
};
const StarRating = (props) => {
    const {channelUrl, total} = props;
    const [rating, setRating] = useState( 0);
    const [selection, setSelection] = useState(0);
    const [msgToSent, setMsgToSent] = useState({msg: "",msgType: "", currentRating:""});
    useEffect(() => {
        if(msgToSent.currentRating){
            setRating(msgToSent.currentRating);
        }
        // eslint-disable-next-line
    }, [msgToSent]);
    const hoverOver = event => {
        if(rating) {
          // do nothing
        } else {
            let val = 0;
            if (event && event.target && event.target.getAttribute('data-star-id')){
                val = event.target.getAttribute('data-star-id');
            }
            setSelection(val);
        }
    };
    const component = !rating && msgToSent.msg ? (<SendMessageWithSendBird msg={msgToSent.msg} customType={msgToSent.msgType}  channelUrl={channelUrl} />) : (<></>);
    const classNameDisabled = rating ? "disabled" : "";
    return (
        <span
            onMouseOut={() => hoverOver(null)}
            onClick={e => {
                if(rating) {
                    return false;
                }
                const currentRating = e.target.getAttribute('data-star-id') || rating;
                setMsgToSent({msg: currentRating, msgType: "answer", currentRating: currentRating});
            }}
            onMouseOver={hoverOver}
            className={"starsRating " + classNameDisabled }
        >
                {Array.from({ length: total }, (v, i) => (
                    <Star
                        starId={i + 1}
                        key={`star_${i + 1}`}
                        marked={selection ? selection >= i + 1 : rating >= i + 1}
                    />
                ))}
            {component}
            </span>
    );
};
export const renderStars = (channelUrl, total) => {
    return () => (
        <StarRating channelUrl={channelUrl} total={total} />
    );
}

Conclusion

Chat now has the ability to render a custom view for message customType. The stars functionality does not use any dependencies, you can easily change the color using CSS. The inspiration for this article was a snippet found here: https://www.30secondsofcode.org/react/s/star-rating

Chat window with star ratings

That’s it for today’s tutorial. Make sure to follow us for other useful guidelines and don’t forget to sign up for 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