Build a chat/messenger in Retool

Hi there, I've been using Retool for a couple of weeks and it's been amazing! :rocket:

I'm building an admin interface for an existing React app, and now I need to find a way to add a chat to it. This chat will work for one-to-one conversations between admins and users. The users will communicate through the one built on the existing app, but the admins need to see the messages and answer them from the Retool interface.

All the data is being stored on Firebase (:fire:), I'm retrieving and sending messages with Firestore queries.

I started to build this chat with plain JavaScript in a custom component which shows the messages, and a form component to send new ones. The most basic stuff is working, but it's not being synced in real time (the query is just running periodically), and there are still some things to implement (i.e. attaching and displaying images, emoji support, aesthetics).

Maybe you have some better ideas to build this thing, because I think this may not be the most practical way to do it.

I'm adding a screenshot to show what is already done.

Can you suggest me some ideas?

Hey there @joel! Sorry for the delay here :confused:

I think your general approach makes sense, but you might be able to get the functionality you need out of existing Retool components instead of building a custom component (e.g. use a text component, image component, etc.). For realtime updates - we don’t support subscriptions to Firebase quite yet unfortunately :confused:

1 Like

Hi justin, thanks for the help! I ended up building it with a mix of Retool components and a custom one.

I think it looks cool, but subscriptions to Firebase would definitively help. Hope it's somewhere in the roadmap :grin:

4 Likes

Nice! That looks great!

Definitely on the roadmap to add realtime support, but nothing concrete yet :confused:

1 Like

Hey there!

We're going to build our admin interface on Retool and have exactly the same goal for the chat component. Are there any updates on the plans for Firebase subscriptions?

Thanks,
Alex.

Sorry @cheparukhin, no updates as of yet. This post from @sesgoe might be helpful though! Real-time Retool - The Goe Getter

hi, Triyng to figure out how you did that :smiley: can you explain the components you used or the main base of app ?

Hi @alicem! A chat integration like this is still not natively supported at the moment, and we currently still don't support realtime updates. Depending on how customizable or external facing you you'd like this to be, we have a comment component that lets you exchange notes between users in an app, but no support for a chat at the moment. In the last sent photo, it seems like there's a text input component with some buttons, and the top-chart portion is a custom component.

1 Like

Hi @retool_team , I did not know that Retoll does not have support realtime updates. I am working on sample usecases that retool supports. I will try some custom components for testing.

Thank you for your fast response. Take Care!!

Hey @joel . How you are able to make this UI for the chat application in retool? Can you share with me the code for the custom component and whatever is included?

1 Like

Hey @joel , great job. Like @ishi8 , I'm interested to know how you ended up doing this UI

Hi @hakimd , @ishi8 and @alicem
Sorry for the late reply, I've stopped updating this project and lost access to Retool since then.
I'll share what I have, although I might not remember exactly how it is working :sweat_smile:

The chat is a custom component:

This is what I have in Model:

{
  "displayText": "Chat",
	"coach": "TODO",
	"client": {{usersTable.selectedRow.data._id}},
	"timezone": {{usersTable.selectedRow.data.timezone}},
	"data": {{getMessages.data}},
  "queryToTrigger": "getUsers",
	"queryToLoadMessages": "loadAllMessages",
	"unreadCoachMessages": {{getCounters.data.unreadCoachMessages}},
}

This is the iFrame code:

<style>
  body {
    margin: 0;
    padding: 10px;
    font-family: 'Overpass', sans-serif;    
  }
  .card-content {
    min-height: 100%;
    background: #ece5dd;
  }
  
  .msg-container {
    width: 90%;
    margin: 10px 0 10px 0;
    white-space: pre-line;
    overflow-wrap: break-word;
  }
  
  .msg-container-right {
    margin-left:10%;
  }
  
  .msg {      
    color: #656565;
    background: #fdf8e2;
    padding: 4px 8px;    
    line-height: 26px;
    font-size: 14px;
    border-radius: 15px 15px 15px 4px;    
    position: relative;
  }
  	
  .message-data-time {
    color: #888888;
    padding-left: 6px; 
    font-size:  12px;
  }
  
  .right-msg {    
    background: white;
    border-radius: 15px 15px 4px 15px;
    color: #455a64;
  }
  
  .scroll-btn, .load-btn {
    display: block;  	
    padding:0.5em;    
    border:0.16em solid rgba(85, 85, 85, 0.7);
    border-radius:2em;
    box-sizing: border-box;
    text-decoration:none;               
    background: rgba(85, 85, 85, 0.7);
    text-align:center;
    transition: all 0.2s;   
    position:fixed;  	
  	right:1%;
  	z-index:9999;
  }
  
  .scroll-btn {
    bottom:1%;
  }
  
  .load-btn {      
  	top:1%;  	
  }
  
  .scroll-btn:hover, .load-btn:hover, {   	    
    background: rgba(255, 255, 255, 0.7);
  }
</style>
<script src="https://cdn.tryretool.com/js/react.production.min.js" crossorigin></script>
<script src="https://cdn.tryretool.com/js/react-dom.production.min.js" crossorigin></script>
<script src="https://unpkg.com/@material-ui/core@3.9.3/umd/material-ui.production.min.js"></script>
<script src="https://rawgit.com/moment/moment/2.2.1/min/moment.min.js"></script>
<script src="https://cdn.rawgit.com/moment/moment-timezone/0.2.2/builds/moment-timezone-with-data.js"></script>


<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Overpass&display=swap" rel="stylesheet">


<div id="react"></div>

<script type="text/babel">
  const { Button, Card, CardContent } = window["material-ui"];
		
  const scrollToBottom = () => {
   document.getElementById("scrollToBottom").scrollIntoView();
	}   
  
  const MyCustomComponent = ({ triggerQuery, model, modelUpdate }) => (
    <Card>
      <CardContent className="card-content">
        <button className="load-btn" onClick={() => triggerQuery(model.queryToLoadMessages)}>➿</button>
        <button className="scroll-btn" onClick={scrollToBottom}>πŸ”½</button>        
        {
          model.data.map((message, index) => {
          const time = moment(message.sentAt).tz(model.timezone).format("ddd, MMM D - hh:mm A");
          return (
            <div className={message.sentBy !== model.client ? "msg-container-right msg-container" : "msg-container"}>
            	<span class="message-data-time">
                {time}                
                {message.sentBy !== model.client && (model.unreadCoachMessages === 0 || model.data.length - index > model.unreadCoachMessages) ? " βœ”": ""}
                {" #" + message._id.slice(0,5)}
  						</span>
            	{message.text && <div className={message.sentBy !== model.client ? " msg right-msg" : " msg left-msg"}>{message.text}</div>}             
              {message.image && <img className="msg-txt-img" width="250" src={message.image}/>}
  					</div>
          )
          })
        }     
        <div id="loadAll"></div>        
        <div id="scrollToBottom"></div>
        
      </CardContent>
    </Card>
  );	

  const ConnectedComponent = Retool.connectReactComponent(MyCustomComponent);
  ReactDOM.render(<ConnectedComponent />, document.getElementById("react"));
</script>

To type an answer I use a TextInput, and to send it just a button that triggers a query sending the text to our DB. After the text is sent, the chat will reload to have the latest messages.

I hope this helps! Let me know if you need me to show something else from the code :+1:

1 Like

Thanks a lot @joel :ok_hand: