Avatar

Reality Check: Crafting a Chatbot UI with React and a Dash of Selling Sunset Glam (Part 2 of 2)

In our last blog, we dived into the world of chatbots with LangChain JS, but we were too busy weaving API magic and crafting message chains. Today, we're taking a detourโ€”picture this as a Selling Sunset-themed episodeโ€”where the drama unfolds not in high-end real estate but within the pixels of our chatbot UI.

And guess what? The inspiration for this coding escapade came from some juicy chats with my friend Mary. We spilled the virtual tea on drama, showbiz, and everything in between. So, grab your coding shades because, inspired by the highs and lows of Selling Sunset, we're about to create a React masterpiece that's as captivating as the latest reality TV saga. Ready to turn your code into a star? Let's dive into the glitzy realm of frontend fun! ๐ŸŒŸ๐Ÿ’ฌ

Below is the result of what we're going to build:

  • Santi
    Santi
    Hey there! Have you checked out the latest season of Selling Sunset? It's wild! ๐ŸŒŸ
  • You
    Oh, totally! ๐Ÿ˜ฑ The drama in that show is on another level. Can you believe the twists this season?
  • Santi
    Santi
    Right?! The real estate deals are insane, but I can't get enough of the office dynamics. Who's your favorite agent? ๐Ÿ˜Ž
  • You
    Definitely Christine. She brings the drama and fashion game every time. ๐Ÿ’ƒ But Amanza is a close second โ€“ such a powerhouse!
  • Santi
    Santi
    Agreed! Christine's outfits are like a fashion show, and Amanza's work ethic is impressive. What do you think of the new listings they're showcasing? ๐Ÿก
  • You
    The houses are incredible! ๐Ÿฐ I wouldn't mind living in one of those mansions. Did you see that $40 million one with the insane pool?
  • Santi
    Santi
    Yeah, that pool was like a private water park! ๐Ÿ˜… If only we could afford houses like that, right? Dream big, Santi!
  • You
    Maybe one day we'll be sipping champagne in our own Hollywood Hills mansion. ๐Ÿฅ‚ Can't wait for the next episode!
  • Santi
    Santi
    Absolutely! See you at the virtual watch party next week?

Before building the UI, let's define the structure of our messages:

const messages = [
{
sender: "Santi",
content:
"Hey there! Have you checked out the latest season of Selling Sunset? It's wild! ๐ŸŒŸ",
},
{
sender: "You",
content:
"Oh, totally! ๐Ÿ˜ฑ The drama in that show is on another level.
Can you believe the twists this season?",
},
...
];

Now, let's build our UI with Tailwind. I won't explain in detail everything I did with Tailwind; I'll save that for another post. For now, the focus is on React. At this point, our component is static and has no interaction:

export default function Chat() {
const messages = [
{
sender: "Santi",
content:
"Hey there! Have you checked out the latest season of Selling Sunset? It's wild! ๐ŸŒŸ",
},
{
sender: "You",
content:
"Oh, totally! ๐Ÿ˜ฑ The drama in that show is on another level.
Can you believe the twists this season?",
},
...
];
return (
<article className="p-6 chat">
<div className="border border-gray-300 rounded-xl p-4 max-w-screen-md">
<ul className="overflow-auto flex flex-col h-[350px] m-0 p-0">
{messages.map((message, index) => (
<li
key={index}
className={"grid grid-cols-12 pt-6"}
>
<div className="col-span-1 flex justify-end">
{message.sender === "Santi" ? (
<Image
priority
src="/images/profile-pic.jpg"
className="rounded-full h-6 w-6 border-[1px] border-slate-400"
height={24}
width={24}
alt={message.sender}
/>
) : (
<UserIcon />
)}
</div>
<div className="col-span-11 flex flex-col pl-4">
<div className="font-semibold">{message.sender}</div>
<div className="font-light">{message.content}</div>
</div>
</li>
))}
</ul>
<form className="flex gap-4 items-center relative pt-4">
<input
required
type="text"
name="message"
placeholder="Message Santi..."
className="border border-gray-300 p-2 rounded-xl flex-1 font-light"
/>
<button
type="submit"
className="bg-gray-600 text-white font-bold rounded-xl h-8 w-8 p-1 absolute right-1 flex items-center justify-center"
>
<div className="w-4 h-4">
<SendIcon />
</div>
</button>
</form>
</div>
</article>
);
}

This is how our component looks like this far:

  • Santi
    Santi
    Hey there! Have you checked out the latest season of Selling Sunset? It's wild! ๐ŸŒŸ
  • You
    Oh, totally! ๐Ÿ˜ฑ The drama in that show is on another level. Can you believe the twists this season?
  • Santi
    Santi
    Right?! The real estate deals are insane, but I can't get enough of the office dynamics. Who's your favorite agent? ๐Ÿ˜Ž
  • You
    Definitely Christine. She brings the drama and fashion game every time. ๐Ÿ’ƒ But Amanza is a close second โ€“ such a powerhouse!
  • Santi
    Santi
    Agreed! Christine's outfits are like a fashion show, and Amanza's work ethic is impressive. What do you think of the new listings they're showcasing? ๐Ÿก
  • You
    The houses are incredible! ๐Ÿฐ I wouldn't mind living in one of those mansions. Did you see that $40 million one with the insane pool?
  • Santi
    Santi
    Yeah, that pool was like a private water park! ๐Ÿ˜… If only we could afford houses like that, right? Dream big, Santi!
  • You
    Maybe one day we'll be sipping champagne in our own Hollywood Hills mansion. ๐Ÿฅ‚ Can't wait for the next episode!
  • Santi
    Santi
    Absolutely! See you at the virtual watch party next week?

What we're interested in now is having our component render each time there's a new message. For that, let's create a state variable using the useState hook and create the handler to submit the form:

const [messages, setMessages] = useState([
{
sender: "Santi",
content:
"Hey there! Have you checked out the latest season of Selling Sunset? It's wild! ๐ŸŒŸ",
},
{
sender: "You",
content:
"Oh, totally! ๐Ÿ˜ฑ The drama in that show is on another level. Can you believe the twists this season?",
},
...
]);
...
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
const form = event.target as HTMLFormElement;
const formData = new FormData(form);
const newMessage = formData.get("message") as string;
if (newMessage) {
setMessages([...messages, { sender: "You", content: newMessage }]);
form.reset();
}
};

Now, we want every time a new message is submitted, the element scrolls into view. To achieve this, we'll use element.scrollIntoView() method. Let's start by adding a ref to the last element in our list of messages:

const lastMessageRef = useRef<HTMLLIElement>(null);
...
<ul className="overflow-auto flex flex-col max-h-[350px] m-0 p-0">
{messages.map((message, index) => (
<li
key={index}
// We add the ref to the last item
ref={index === messages.length - 1 ? lastMessageRef : null}
className={"grid grid-cols-12 pt-6"}
>
...
</li>
))}
</ul>

We know that scrollIntoView() has a side effect. As React won't update the ref of our last element until after updating the DOM, we won't use an event handler; we'll have to use the useEffect hook.

NOTE: If you wonder why we're not putting the useEffect argument with the dependency array, it's for two reasons: our ref is not a dependency, and we want it to scroll every time there's a render.

useEffect(() => {
if (lastMessageRef.current) {
lastMessageRef.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'start'
});
}
});

Ta-da! Our chatbot UI is ready for action! This is how the complete code looks. ๐Ÿš€ But hold the popcorn; we've got some blockbuster enhancements in mind:

  1. Word Unveiling Magic: Add animations that simulate words being typed, turning each message into a virtual cliffhanger.
  2. Silence the Empty Chatter: Disable the send button when the user forgets to spill the teaโ€”no more accidental empty messages.
  3. Inclusive Glam: Sprinkle ARIA attributes for an inclusive and seamless chatbot experience.
Alice falling down the rabbit hole

Our chatbot might be drama-ready, but we're turning it into a blockbuster. Think of it as a coding reality showโ€”each enhancement reveals a new twist. Go on, code like it's a lifestyle, and let the chatbot saga continue! ๐Ÿ’ฌโœจ