# Building an interactive chatbot interface with React
In today's digital age, [chatbots](https://en.wikipedia.org/wiki/Chatbot) have revolutionized communication by providing automated and efficient support to businesses and individuals. In this article, we will explore how to leverage React, a popular JavaScript library, to develop chatbots with enhanced conversational experiences.
## Creating chatbot interactions with React
React, a JavaScript library, offers a powerful foundation for crafting dynamic web applications. When harnessed for chatbot development, React enables the creation of intuitive, responsive, and visually appealing conversational interfaces. This article delves into the intricacies of developing shopping assistant chatbots with React, which includes integrating [Natural Language Processing (NLP)](https://www.techtarget.com/searchenterpriseai/definition/natural-language-processing-NLP) services to make our chatbots smarter and focusing on design considerations, user input handling, conversation flow, and more.
### Factors for conversational design and user experience
Conversational design and user experience are critical aspects of developing effective and engaging chatbots. The design of the conversation and the overall user experience can greatly impact how users perceive and interact with the chatbot. Here are some key factors to consider when designing conversations and ensuring a positive user experience:
- **Clarity and simplicity:**
The language used by the chatbot should be clear, concise, and easily understandable. Avoid jargon and complex terms that might confuse users. Keep the conversation simple.
- **Empathy and tone:**
Design the chatbot's responses to reflect a friendly and empathetic tone. Users are more likely to engage with a chatbot that feels personable and considerate.
- **User-centered approach:**
Understand the needs, preferences, and pain points of your target audience. Design the conversation flow based on user goals and expectations. The chatbot should be built to assist users in achieving their objectives.
- **Prompt and contextual responses:**
Ensure that the chatbot provides timely responses to user inputs. Responses should be relevant to the context of the ongoing conversation. Use context cues to refer to previous interactions and maintain the flow of the conversation.
- **Feedback and validation:**
Inform users when their input has been received and understood. Provide validation or acknowledgment of their actions to give them confidence that the chatbot is responsive.
- **User guidance:**
Help users understand how to interact with the chatbot by offering clear instructions or prompts. Use visual cues like buttons, suggestions, and examples to guide users through the conversation.
- **Error handling:**
Design the chatbot to handle errors and misunderstandings gracefully. If a user's input is unclear or not recognized, provide options for clarification or suggest alternative phrasing.
- **Multi-turn conversations:**
Design the chatbot to handle multi-turn conversations seamlessly. Keep track of the conversation history to maintain context and provide coherent responses.
- **User control:**
Allow users to guide the conversation. Provide options for them to choose from, and avoid forcing a linear flow. Let users change topics, backtrack, or skip sections if needed.
- **Visual design:**
If the chatbot has a graphical interface, pay attention to visual design elements. Use colors, typography, and layout to enhance readability and create a visually pleasing experience.
- **Testing and iteration:**
Continuously test the chatbot with real users to gather feedback and identify areas for improvement. Iterate on the design based on user input to enhance the conversational experience over time.
### Building conversational `UI` components for the shopping assistant chatbot
This chatbot serves as a personalized and efficient shopping companion that can be seamlessly embedded within a company's digital platforms, including the website and mobile app. Utilizing Natural Language Processing (NLP) facilitates user-friendly conversational engagements by allowing users to input text. The main goal of the chatbot is to improve the user experience by offering individualized help in real-time. Its many advantages include improved customer happiness and engagement, more sales via tailored recommendations, effective customer service, and enhanced customer loyalty. As we go into the creation process of our chatbot shopping assistant, we will present six key files that will improve user engagement, improve chat functionality, and eventually provide an exceptional user experience on the website. These files, which will be added to the ones already present in our React project, are `component.css`, `container.js`, `.env`, `dialogflow.js`, `mock-data.js`, and `utils.js`. Let's examine the file structure of our project in more detail.
```
project root
├── src
│ ├── api
| | |___ dialogflow.js
| | |
│ ├── components
| | |___ component.css
| | |___ container.js
│ | |___ utils.js
| | |
| App.css
| App.js
| App.test.js
| mock-data.js
| index.css
| index.js
| |
.env |
| |
```
Note that the project should be viewed on a big screen (e.g., desktop, laptop, etc.), and it is not responsive.
Having created these files let's edit them with the appropriate code needed for our chatbot.
First, let's modify all the css files for this project. `src/App.css` and `src/components/component.css`
`src/App.css` file
```css
.chatbot {
margin-left: auto;
margin-right: auto;
width: 25%;
position: relative;
top: 7px;
height: 98vh;
background-color: white;
border-radius: 1em;
}
```
`src/components/component.css` file
```css
.container {
display: flex;
flex-direction: column;
height: 95vh;
}
/** header **/
.header {
position: relative;
top: 20px;
margin-bottom: 5%;
}
.firstlayer {
border-bottom: 1px solid #007bff;
display: flex;
margin: 10px 0 10px 0;
}
.firstlayer p {
justify-content: center;
align-items: center;
font-family: "Trebuchet MS", "Lucida Sans Unicode", "Lucida Grande",
"Lucida Sans", Arial, sans-serif;
}
img {
width: 70px;
height: 40px;
border-color: white;
}
/** body **/
.body {
height: 80vh;
margin-bottom: 5%;
overflow-y: auto;
overflow-x: hidden;
}
.message-user {
color: white;
max-width: 60%;
border-radius: 5px;
padding: 8px;
background-color: grey;
margin: 10px;
clear: both;
}
.message-bot {
color: white;
max-width: 60%;
border-radius: 5px;
padding: 8px;
background-color: #007bff;
margin: 10px;
float: right;
}
/** footer **/
.footer {
display: flex;
justify-content: center;
align-items: center;
}
.inputbox {
display: flex;
justify-content: space-between;
width: inherit;
}
.ask {
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 16px;
outline: none;
transition: border-color 0.3s;
}
.ask:focus {
border-color: #007bff;
}
::-webkit-scrollbar {
width: 5px;
background: transparent;
}
::-webkit-scrollbar-thumb {
background: coral;
border-radius: 2em;
min-height: 5px;
}
```
Because we will not be using any external api to request for available products we are going to create a mock data. Lets modify our `mock-data.js` for the appropiate data
`mock-data.js` File
```javascript
const mockData = {
book: [
{
name: "Innovation",
price: "£24.99",
summary:
"A groundbreaking book that explores the strategies and principles behind successful innovation in business.",
},
{
name: "Development",
price: "£19.95",
summary:
"An essential guide for web developers. Learn advanced web development techniques, including modern CSS, JavaScript, and responsive design.",
},
{
name: "Godzilla",
price: "£12.99",
summary:
"Embark on a thrilling adventure with detective Emily as she solves the puzzling mystery of the lost key.",
},
],
phone: [
{
name: "ProX",
price: "£799.00",
summary:
"Experience the pinnacle of technology with the Smartphone Pro X. This flagship device offers cutting-edge features, stunning camera capabilities, and a sleek design.",
},
{
name: "Phone3",
price: "£199.99",
summary:
"Get the best value with the Budget-Friendly Phone 3. Despite its affordable price, this smartphone delivers a responsive performance, decent camera, and long battery life.",
},
{
name: "CameraKing",
price: "£699.99",
summary:
"For photography enthusiasts, the Camera King Phone is a dream come true. With exceptional camera features, you can capture stunning photos and videos with ease.",
},
],
computer: [
{
name: "Workstation",
price: "£1999.00",
summary:
"The Ultimate Workstation PC is a powerhouse for professionals. It offers top-tier performance, extensive storage, and exceptional graphics capabilities for demanding tasks.",
},
{
name: "StylishLaptop",
price: "£1299.00",
summary:
"Achieve elegance and productivity with the Slim and Stylish Laptop. Its sleek design, high-resolution display, and long battery life make it a versatile choice for work and play.",
},
{
name: "GamersParadise",
price: "£1599.00",
summary:
"Experience gaming like never before with the Gamer's Paradise PC. This gaming rig boasts cutting-edge components, customizable RGB lighting, and impressive graphics for immersive gameplay.",
},
],
};
export default mockData;
```
Next, let's modify our `src/App.js` File
```jsx
import { Container } from "./components/container";
import React from "react";
import "./App.css";
const Chatbot = () => {
return (
<div className="chatbot">
<Container />
</div>
);
};
export default Chatbot;
```
When we run this project an error will be thrown. To avoid this error let's modify the necessary files which are: `src/components/utils.js` and `src/components/container.js`
`src/components/utils.js` file
```jsx
import React from "react";
import logo from "../logo.svg";
export const Message = ({ text, isUser }) => {
return <div className={isUser ? "message-user" : "message-bot"}>{text}</div>;
};
export const InputBox = ({ onChange, onSubmit, userInput }) => {
return (
<form onSubmit={onSubmit}>
<div className="inputbox">
<input
className="ask"
placeholder="Ask me"
type="text"
value={userInput}
onChange={onChange}
/>
<button
style={{
marginLeft: "15px",
backgroundColor: "#007bff",
color: "white",
border: "none",
borderRadius: "5px",
}}
type="submit"
>
Send
</button>
</div>
</form>
);
};
export const Header = () => {
return (
<div className="firstlayer">
<img src={logo} alt="OpenReplay Logo" />
<p>Chat with OpenReplay</p>
</div>
);
};
```
Next, let's modify the `src/components/container.js`
```jsx
import React from "react";
import { Header } from "./utils";
import "./component.css";
export const Container = () => {
return (
<div className="container">
<div className="header">
<Header />
</div>
</div>
);
};
```
When you run your development server this is what you should see.

Now let's add the `Message` component to our `container.js` . This is where all conversations will be displayed.
Modified `src/components/container.js`
```jsx
import React, { useEffect, useRef } from "react";
import { Message, Header } from "./utils";
import "./component.css";
export const Container = () => {
const ref = useRef({});
useEffect(() => {
ref.current.scrollTop = ref.current.scrollHeight;
}, []);
return (
<div className="container">
<div className="header">
<Header />
</div>
<div ref={ref} className="body">
<Message key={0} text={"Hello"} isUser={true} />
</div>
</div>
);
};
```
Running your development server this is what you should see.

Finally for our `src/components/container.js` let's add the `InputBox` component. This is where users can input their requests. It is like the input element in `HTML`.
Modified `src/components/container.js`
```jsx
import React, { useEffect, useRef } from "react";
import { Message, InputBox, Header } from "./utils";
export const Container = () => {
const ref = useRef({});
useEffect(() => {
ref.current.scrollTop = ref.current.scrollHeight;
}, []);
return (
<div className="container">
<div className="header">
<Header />
</div>
<div ref={ref} className="body">
<Message key={0} text={"Hello"} isUser={true} />
</div>
<div className="footer">
<InputBox />
</div>
</div>
);
};
```
Refreshing your development server this is what you should see.

The files we have just modified are files that take care of the `UI`. But for the `API` communication we need to modify the `src/api/dialogFlow.js` file.
`src/api/dialogFlow.js` file
```jsx
import axios from "axios";
import { gapi } from "gapi-script";
// DialogFlow API endpoint URL
const { REACT_APP_DIALOG_FLOW_URL: dialogflowUrl } = process.env;
async function getAccessToken() {
const auth2 = await gapi.auth2.getAuthInstance();
await auth2.signIn();
const user = auth2.currentUser.get();
const { access_token: accessToken } = user.getAuthResponse();
return accessToken;
}
async function detectIntent({
token_exist,
projectId,
sessionId,
queryInput,
token,
}) {
const URL = `${dialogflowUrl}/${projectId}/agent/sessions/${sessionId}:detectIntent`;
const accessToken = token_exist ? token : await getAccessToken();
const headers = {
"Content-Type": "application/json; charset=utf-8",
Authorization: `Bearer ${accessToken}`,
};
try {
const response = await axios.post(URL, queryInput, { headers });
return { data: response.data, token: accessToken };
} catch (err) {
return detectIntent({
token_exist: false,
projectId,
sessionId,
queryInput,
token,
});
}
}
export default detectIntent;
```
In the file above, the `getAccessToken` function returns an access token that will be used for authentication while the `detectIntent` function is used to communicate with the `dialogFlow` instance created in Google Cloud. We will see how to create that later in this article. But because we have not modified our `.env` file the `REACT_APP_DIALOG_FLOW_URL` value cannot be accessed. To avoid this, let's modify our `.env` file
Modified `.env` file
```env
REACT_APP_DIALOG_FLOW_URL = https://dialogflow.googleapis.com/v2/projects
```
### Managing chat state with React hooks
React Hooks provide a convenient way to manage states within functional components. We can use the `useState` hook to handle the chat history and user inputs. This will be done inside the `src/App.js` file.
Updated `src/App.js` file
```jsx
import { Container } from "./components/container";
import React, { useState } from "react";
import "./App.css";
const Chatbot = () => {
const [chatHistory, setChatHistory] = useState([]);
const [userInput, setUserInput] = useState("");
const handleInputChange = (event) => {
setUserInput(event.target.value);
};
const handleFormSubmit = (event) => {
event.preventDefault();
if (userInput) {
setChatHistory([...chatHistory, { text: userInput, isUser: true }]);
setUserInput("");
}
};
return (
<div className="chatbot">
<Container
chatHistory={chatHistory}
onFormSubmit={handleFormSubmit}
onInputChange={handleInputChange}
userInput={userInput}
/>
</div>
);
};
export default Chatbot;
```
With our `src/App.js` being updated, let's modify our `src/components/container.js`
Updated `src/components/container.js`
```jsx
import React, { useEffect, useRef } from "react";
import { Message, InputBox, Header } from "./utils";
import "./component.css";
export const Container = ({
chatHistory,
onFormSubmit,
onInputChange,
userInput,
}) => {
const ref = useRef({});
useEffect(() => {
ref.current.scrollTop = ref.current.scrollHeight;
}, [chatHistory]);
return (
<div className="container">
<div className="header">
<Header />
</div>
<div ref={ref} className="body">
{chatHistory.map((message, index) => (
<Message key={index} text={message.text} isUser={message.isUser} />
))}
</div>
<div className="footer">
<InputBox
onChange={onInputChange}
userInput={userInput}
onSubmit={onFormSubmit}
/>
</div>
</div>
);
};
```
With our `src/components/container.js` and `src/App.js` being updated we can now manage effectively our states.
## Integrating NLP and external `APIs` for smarter chatbot
To enhance the intelligence of our chatbots, we will integrate Natural Language Processing (NLP) services and external `APIs`. Let's explore how to integrate `Dialogflow`, a popular NLP service, with our React chatbot. To use `dialogFlow` we have to install some dependencies. Below is the command to install these dependencies into our React project
```console
npm install axios gapi-script
```
Before using`dialogFlow` in our project, there are some configurations we need to set up in our `dialogFlow` account but that is outside the scope of this article. For the configuration watch this [video](https://www.youtube.com/watch?v=jeDnHARmW34). Also, we need to configure some credentials on the Google Cloud for authentication. To configure these credentials watch this [video](https://youtu.be/HtJKUQXmtok?si=YmydZIft7pGtNcuv) and save the `clientId` in your `.env` file. Here's how our `.env` file will look:
`.env` file
```env
REACT_APP_PROJECT_ID = <PROJECT_ID> // project id gotten after creating our Dialogflow in the Google Cloud
REACT_APP_OPEN_KEY = <CLIENT_ID> // client ID got after creating our credentials for authentication (OAUTH2).
REACT_APP_DIALOG_FLOW_URL = https://dialogflow.googleapis.com/v2/projects
```
After updating our`.env` file let's update our `src/App.js` file to effectively integrate our Natural Language Processing (NLP) services.
`src/App.js` file
```jsx
import { Container } from "./components/container";
import React, { useState, useEffect } from "react";
import { v4 as uuidv4 } from "uuid";
import { gapi } from "gapi-script";
import detectIntent from "./api/dialogflow";
import "./App.css";
const Chatbot = () => {
const [chatHistory, setChatHistory] = useState([]);
const [userInput, setUserInput] = useState("");
const [token, setToken] = useState("");
const { REACT_APP_PROJECT_ID: projectId } = process.env;
const { REACT_APP_OPEN_KEY: OPEN_KEY } = process.env;
const handleDialogflow = async () => {
const sessionId = uuidv4();
const queryInput = {
queryInput: {
text: {
text: userInput,
languageCode: "en-US",
},
},
};
const { data: response, token: accessToken } = await detectIntent({
token_exist: token !== "",
projectId,
sessionId,
queryInput,
token,
});
setToken(accessToken);
const diagReply = response.queryResult.fulfillmentText;
setChatHistory((chatHistory) => [
...chatHistory,
{ text: diagReply, isUser: false },
]);
};
const handleInputChange = (event) => {
setUserInput(event.target.value);
};
const handleFormSubmit = (event) => {
event.preventDefault();
if (userInput) {
setChatHistory([...chatHistory, { text: userInput, isUser: true }]);
setUserInput("");
handleDialogflow();
}
};
useEffect(() => {
const init = () => {
gapi.client.init({
clientId: OPEN_KEY,
scope: "https://www.googleapis.com/auth/dialogflow",
});
};
gapi.load("client:auth2", init);
}, [OPEN_KEY]);
return (
<div className="chatbot">
<Container
chatHistory={chatHistory}
onFormSubmit={handleFormSubmit}
onInputChange={handleInputChange}
userInput={userInput}
/>
</div>
);
};
export default Chatbot;
```
After editing all files here's how our chatbot will look:

## Managing conversation flow and context
To guide the conversation flow for the shopping assistant chatbot, we can implement a state machine approach using React's state management capabilities. By tracking the chatbot's state and transitioning between states based on user inputs, we can create dynamic and engaging interactions. Lets modify our `src/App.js` file using the `dialogFlow` customized entities. If you don't understand what is `dialogFlow` customized entities make sure you watch this [video](https://www.youtube.com/watch?v=jeDnHARmW34).
Modified `src/App.js` file
```jsx
import { Container } from "./components/container";
import React, { useState, useEffect } from "react";
import { v4 as uuidv4 } from "uuid";
import { gapi } from "gapi-script";
import detectIntent from "./api/dialogflow";
import "./App.css";
import mockData from "./mock-data";
const Chatbot = () => {
const [chatHistory, setChatHistory] = useState([]);
const [userInput, setUserInput] = useState("");
const [token, setToken] = useState("");
const [recommendation, setRecommendation] = useState("");
const { REACT_APP_PROJECT_ID: projectId } = process.env;
const { REACT_APP_OPEN_KEY: OPEN_KEY } = process.env;
const conversation_state = {
"product.inquiry": "PRODUCT_INQUIRY",
"product.details": "PRODUCT_DETAILS",
"post_purchase.support": "POST_PURCHASE_SUPPORT",
};
const processUserInput = async (diagReply, state, param) => {
switch (state) {
case "PRODUCT_INQUIRY":
// Process PRODUCT_INQUIRY and respond to user
const param_to_lowercase = param.toLowerCase();
diagReply =
"Thank you for your inquiry. Unfortunately, we don't have the product you're looking for. Can you please ask for another recommendation? We'd be happy to assist you.";
if (param_to_lowercase in mockData) {
setRecommendation(param_to_lowercase);
diagReply = "";
mockData[param_to_lowercase].map((itm, i) => {
diagReply += `${itm.name}<br/>`;
return itm;
});
diagReply +=
"<br/>Which one would you like me to give you more details on?";
}
setChatHistory((chatHistory) => [
...chatHistory,
{ text: diagReply, isUser: false },
]);
break;
case "PRODUCT_DETAILS":
// Process PRODUCT_DETAILS
diagReply =
"Sorry, we can't find more details for the product you have selected.";
if (recommendation) {
const product = mockData[recommendation].find(
(itm) => itm.name === param
);
if (product) {
diagReply = `<b>Name</b>: ${product.name}<br/><b>Price</b>: ${product.price}<br/><b>Summary</b>: ${product.summary}`;
}
}
setChatHistory((chatHistory) => [
...chatHistory,
{ text: diagReply, isUser: false },
]);
break;
case "POST_PURCHASE_SUPPORT":
// Process POST_PURCHASE_SUPPORT
const list_of_rec = Object.keys(mockData).filter(
(itm) => itm !== recommendation
);
diagReply = `Thank you for considering our recommendation in ${recommendation}. We're thrilled to have been of help! If you're looking for recommendations in other areas, such as ${list_of_rec
.slice(0, 3)
.toString()} or more, we'd be delighted to assist further`;
setChatHistory((chatHistory) => [
...chatHistory,
{ text: diagReply, isUser: false },
]);
break;
default:
//pass
break;
}
};
const handleDialogflow = async () => {
const sessionId = uuidv4();
const queryInput = {
queryInput: {
text: {
text: userInput,
languageCode: "en-US",
},
},
};
const { data: response, token: accessToken } = await detectIntent({
token_exist: token !== "",
projectId,
sessionId,
queryInput,
token,
});
const param =
response.queryResult.parameters[
Object.keys(response.queryResult.parameters || {})
];
setToken(accessToken);
const state = conversation_state[response.queryResult.action];
const diagReply = response.queryResult.fulfillmentText;
setChatHistory((chatHistory) => [
...chatHistory,
{ text: diagReply, isUser: false },
]);
setTimeout(() => {
processUserInput(diagReply, state, param);
}, 3000);
};
const handleInputChange = (event) => {
setUserInput(event.target.value);
};
const handleFormSubmit = (event) => {
event.preventDefault();
if (userInput) {
setChatHistory([...chatHistory, { text: userInput, isUser: true }]);
setUserInput("");
handleDialogflow();
}
};
useEffect(() => {
const init = () => {
gapi.client.init({
clientId: OPEN_KEY,
scope: "https://www.googleapis.com/auth/dialogflow",
});
};
gapi.load("client:auth2", init);
}, [OPEN_KEY]);
return (
<div className="chatbot">
<Container
chatHistory={chatHistory}
onFormSubmit={handleFormSubmit}
onInputChange={handleInputChange}
userInput={userInput}
/>
</div>
);
};
export default Chatbot;
```
In the code above we've strategically employed three distinct conversation contexts to ensure a smooth and coherent interaction with users. These contexts are:
- **PRODUCT_INQUIRY:**
This context handles the initial phase of the conversation when users inquire about specific products. It's responsible for presenting product recommendations and capturing the user's selection.
- **PRODUCT_DETAILS:**
The `PRODUCT_DETAILS` context comes into play when users express interest in learning more about a particular product. It provides comprehensive information, including specifications and customer reviews, helping users make informed decisions.
- **POST_PURCHASE_SUPPORT:**
This context aims to express appreciation for the interaction, extending an invitation for users to seek further recommendations or assistance, ensuring a comprehensive and supportive customer experience beyond the initial purchase.
By structuring the conversation around these three contexts, we create a well-organized and user-friendly chatbot experience. Each context serves a specific purpose in the shopping journey, ensuring that users receive relevant information and assistance at each step. This approach enhances the overall user experience and streamlines the process of finding and purchasing products through the chatbot.
Here is a link to the [video](https://shorturl.at/nDE26) that shows our trained dialogFlow and the data we used to train it.
This is how a user can interact with the chatbot.

To access the source code for this project, please visit the [GitHub repository](https://github.com/m-malight/shopping-assistant).
## Conclusion
By harnessing the capabilities of React and seamlessly integrating Natural Language Processing (NLP) services, we can create interactive chatbot interfaces that offer unparalleled conversational experiences. React empowers us to design intuitive and user-friendly interfaces, providing users with a seamless interaction. The incorporation of NLP services elevates the chatbot's comprehension of user requests, allowing for more precise and relevant responses. Furthermore, effective context management ensures a coherent conversation flow, enabling personalized interactions that enhance the overall user experience. With these technologies at our disposal, we can build chatbot interfaces that not only assist users but also enrich their online interactions.