React multi-user chat with Chatix backend

Chatix chatroom







Now I will show you how to make a chat for the team / users / friends if you do not have your backend or do not want to waste time developing it. We will make a simple text chat and it will take us about an hour.







To write a working network chat without a backend is almost impossible, it must be in one form or another. We will use Chatix and its JavaScript SDK. Chatix and SDK will be engaged in message storage and network tasks, and we will deal with the front-end.







The finished project code is available on GitHub

Demo







Project structure





Important note about state storage. Of course, it would be more convenient to add Redux here and process state changes through it, but in order to save time, we will store the state in the root component of the App and will forward data to the child components and call their parents from the child methods.

For example, when we get the name of the chat, we will save it in the state of the App component and pass it through props



: App → Header → RoomTitle



. When the user writes a message, we will send it from SendMessageForm to the App: SendMessageForm → ChatField → Main → App



.

Our chat will look something like this in design:







chatix.io







Interaction between components



Our components must transmit data to each other and in order for everything to work as it should, let's determine now how they will interact with each other.







Component interaction







As you can see in the figure, the main component for us is the App



, which provides data to the child components (due to the reactivity, we simply assign prop



and the child component will respond to changes), and the child components successively forward method calls to the App



. This is not the best architecture that can (and should) be done for a production project, but it will do for our lesson.







Project creation



Create visual components



Hat



  1. First you need to create a new project, for this we will use create-react-app .







     npx create-react-app chatix-chatroom cd chatix-chatroom
          
          





    Run the project with the command







     npm start
          
          





  2. Let's start by creating a header.

    First, add a logo to the header. To do this, inside the src folder, create the components folder, and in it the logo_header folder. We upload the logo to this folder and create 2 LogoHeader.js and LogoHeader.css files









LogoHeader.js







 import React from 'react' import logo from './chatix_logo.svg'; import './LogoHeader.css'; function LogoHeader(){ return ( <div className="LogoHeader"> <img src={logo} className="App-logo" alt="Chatix logo" /> </div> ); } export default LogoHeader;
      
      





LogoHeader.css







 .LogoHeader{ flex-basis: 200px; flex-grow: 0; flex-shrink: 0; }
      
      





Everything is clear here, in this component a file with a logo and styles is simply imported.







I will not add the code of the style files here anymore, you can see them on the page of the finished project







Now display the name of the chat room. To do this, create a room-title folder and in it the RoomTitle component. We will throw the name into this component through props, so we write props.chatroomName



and now we will transfer it here.







Roomtitle







 import React from 'react'; import './RoomTitle.css'; function RoomTitle(props){ return ( <div className="RoomTitle"> <h1>{props.chatroomName}</h1> </div> ); } export default RoomTitle;
      
      





Then we create the header component itself and place the logo and the name of the chat room in it. Immediately throw the chat name into the child component through prop chatroomName



.







I remind you that we agreed that all the data (application state) will be stored by the root component of the App . From it we will transfer the header first to Header and from Header to RoomTitle .







components \ header \ Header.js







Header.js







 import React from 'react'; import './Header.css' import LogoHeader from '../logo_header/LogoHeader'; import RoomTitle from '../room-title/RoomTitle'; function Header(props) { return ( <header> <LogoHeader/> <RoomTitle chatroomName={props.chatroomName} /> </header> ); } export default Header;
      
      





Next, open the App.js file and add the Header.js component to it .

Then in the state we add the name and through props we forward it to the header.

Also in the header you need to add the name of the current user. To do this, add the user object to the state and similarly forward it to the header







 import React from 'react'; import './App.css'; import Header from './components/header/Header'; class App extends React.Component { constructor(props){ super(props); chatroomName: '-', me: { is_online: true, name: "", uuid: "98s7dfh9a8s7dhf" } } render() { return ( <div className="App"> <Header chatroomName={this.state.chatroomName} me={this.state.me} /> </div> ); }; } export default App;
      
      





Now in the header you need to add an input with the name of the current user and assign the handler to change so that we can transfer the new username to the App component.







To do this, we add the handleChangeName



handler function to the input with the name and call the props.updateVisitor



callback function in which we pass the user object with the updated name.







Header.js







 function Header(props) { const [name, setName] = useState(props.me.name ? props.me.name : props.me.uuid.substr(-10)) const handleChangeName = (e) => { setName(e.target.value) let visitor = {...props.me}; visitor.name = e.target.value; props.updateVisitor(visitor) } return ( <header> <LogoHeader/> <RoomTitle chatroomName={props.chatroomName}/> { props.me ? <input className='name-input' value={name} placeholder=' ' onChange={(e) => handleChangeName(e)} /> : null } </header> ); }
      
      





Now add this function props.updateVisitor



to the App and props.updateVisitor



it to the props.updateVisitor



. So far, it just updates the user object in the state, but then through it we will update the user on the server.







 onUpdateVisitor = (visitor) => { this.setState({me: visitor}) }
      
      





So, now our application looks like this and so far only knows how to update the name. Go ahead







Chatroom header







Sidebar



Now let's get down to creating a sidebar.

The sidebar will be located inside the main component on the Main.js page.

We create it components \ main \ Main.js , then create a component with a list of users components \ member-list \ MemberList.js and immediately create a component that will display the users themselves components \ member-item \ MemberItem.js .







To make it clearer how these 3 components are related, take a look at the project diagram at the beginning of the article.







The components are created, now let's go in order.

First, add the array of users to the state of the App component and add the Main component. Then we will forward these users into it.







The app







 class App extends React.Component { constructor(props) { super(props); this.state = { chatroomName: '-', members: [ { is_online: true, name: "", uuid: "98s7dfh9a8s7dhf" }, { is_online: true, name: "", uuid: "mnzxcv97zx6chvo" }, { is_online: false, name: "", uuid: "kjuhv987ashdfoua" }, { is_online: false, name: "", uuid: "jdhnf978WEHJSNDL" }, ], me: { is_online: true, name: "", uuid: "98s7dfh9a8s7dhf" } }; } render() { return ( <div className="App"> <Header chatroomName={this.state.chatroomName} me={this.state.me} /> <Main members={this.state.members} me={this.state.me} /> </div> ); }; }
      
      





In the Main component, add the MemberList component and forward the array of users into it.







Main.js







 function Main(props) { return( <section className="Main"> <MemberList members={props.members} /> </section> ); }
      
      





And in the MemberList component , we loop through all users and for each return the MemberItem component and pass the user object to it.







MemberList.js







 function MemberList(props) { const members = props.members.map((member) => <MemberItem key={member.uuid} member={member} /> ); return ( <section className="MemberList"> {members} </section> ); }
      
      





The MemberItem component is already directly displaying the user in the sidebar. In it we check the user’s name, if it is not installed, then display the first 10 characters of the identifier. We also check the status online / offline and compare the identifier with the identifier of the current user, so that in front of him to display the mark "(You)".







 function MemberItem(props) { function getName(){ let name = '' if (props.member.uuid === props.me.uuid) { if(props.me.name) { name = props.me.name } else { name = props.me.uuid.substring(props.me.uuid.length-10, props.me.uuid.length); } } else { if(props.member.name){ name = props.member.name } else { name = props.member.uuid.substring(props.member.uuid.length-10, props.member.uuid.length); } } return name; } return( <div className="MemberItem"> <img src={ icon } alt={ props.member.name }/> <span> { getName() } { props.member.uuid === props.me.uuid && " () " } </span> { props.member.is_online && <span className="online">•</span> } </div> ); }
      
      





Done. Now the application looks like this







Chatroom header







Message List and Submission Form



Now we’ll deal with the message list and the sending form.

First, add an array with messages to the state of the App component.







The app







 this.state = { chatroomName: '-', messages: [ { content: " 1", sender_id: "mnzxcv97zx6chvo", uuid: "dg897sdfg" }, { content: " 2", sender_id: "98s7dfh9a8s7dhf", uuid: "8723hernm" }, { content: "  ", sender_id: "mnzxcv97zx6chvo", uuid: "435nbcv98234" } ], members: [ { is_online: true, name: "", uuid: "98s7dfh9a8s7dhf" }, { is_online: true, name: "", uuid: "mnzxcv97zx6chvo" }, { is_online: false, name: "", uuid: "kjuhv987ashdfoua" }, { is_online: false, name: "", uuid: "jdhnf978WEHJSNDL" }, ], me: { is_online: true, name: "", uuid: "98s7dfh9a8s7dhf" } };
      
      





And forward them to the Main component







The app







  <Main members={this.state.members} messages={this.state.messages} me={this.state.me} />
      
      





Now create the component conponents / chat-field / ChatField.js

Connect it to Main and forward the messages to it.







Main







 function Main(props) { return( <section className="Main"> <MemberList me={props.me} members={props.members} /> <ChatField messages={props.messages} /> </section> ); }
      
      





Next, create the component conponents / message-container / MessageContainer.js

Connect it to ChatField and also forward messages to it.







Chatfield







 function Main(props) { return( <section className="Main"> <MemberList me={props.me} members={props.members} /> <ChatField messages={props.messages} /> </section> ); }
      
      





Next, we will loop through all the messages and for each return the component that will show it.

Let's create it conponents / message / Message.js . In it we display the visitor’s icon, his name or identifier if no name is indicated and the message text itself.







Message







 function Message(props) { const getSenderName = () => { if (props.sender) { return props.sender.name ? props.sender.name : props.sender.uuid.substr(-10); } return "Unknown sender"; }; return( <div className="Message"> <div className="message-sender-icon"> <img src={icon} alt="visitor icon"/> </div> <div className="message-bubble"> <div className="message-sender-name">{getSenderName()}</div> <div className="message-content">{props.message.content}</div> </div> </div> ); }
      
      





Now, in the MessageContainer, we loop through all the messages and for each return the Message component to which we pass the message object







Messagecontainer







 function MessageContainer(props) { const messageList = props.messages.map(message => <Message key={message.uuid} sender={props.members.find((member) => member.uuid === message.sender_id)} message={message} /> ); return ( <section className="MessageContainer" ref={messagesContainer}> {messageList} </section> ); }
      
      





Now the project looks like this:







Chatroom messages







Now create a component with a form for sending messages components / send-message-form / SendMessageForm.js . In it, we will create an input and a button for sending. When the input is changed, the text from it is written to the state, and when you click on the button, we call the onSendNewMessage



callback function and transfer the message from the state to it. onSendNewMessage



will create the onSendNewMessage



function a bit later in the App component and forward it through props.







Sendmessageform







 class SendMessageForm extends React.Component { constructor(props) { super(props); this.state = { message: '' }; } currentMessageChanged = (e) => { this.setState({message: e.target.value }); } sendMessageClicked = async (e) => { e.preventDefault(); if (this.state.message.length > 0) { await this.props.onSendNewMessage(this.state.message); this.setState({...this.state, ...{message : ''}}); } } render(){ return ( <section className="SendMessageForm"> <form> <input type="text" value={this.state.message} onChange={this.currentMessageChanged} placeholder="Type message to send"/> <button type="submit" onClick={this.sendMessageClicked} > Send </button> </form> </section> ); } }
      
      





Now place the SendMessageForm component inside the ChatField .







Chatfield







 function ChatField(props) { return( <section className="ChatField"> <MessageContainer members={props.members} messages={props.messages} /> <SendMessageForm onSendNewMessage={props.onSendNewMessage}/> </section> ); }
      
      





In the Main component, we will also onSendNewMessage



function in the ChatField .







Main







 <ChatField members={props.members} messages={props.messages} onSendNewMessage={props.onSendNewMessage} />
      
      





Now create this function in the App and forward it to Main .







The app







 onSendNewMessage = async (message) => { console.log(message) }
      
      





The app







 <Main members={this.state.members} messages={this.state.messages} onSendNewMessage={this.onSendNewMessage} me={this.state.me} />
      
      





Done. Now, when you click on the send message button, it will be transferred to the App component.

Now the application looks like this:







Chatroom final







So, now everything is displayed in our application and everything works as it should, but so far with static data, and in order to revitalize our chat, you need to connect it with the backend.







Backend connection



To do this, the first thing you need to do is install the chatix-core package.







 npm i chatix-core
      
      





Then create an account on chatix and create a chat room. To do this, go to chatix.io and register.

After registration, you can see the websiteId site identifier in the admin interface on the chat settings page.







Now create a new chat room with which we will work.







Creating a chat room







We return to our project and create a new component through which we will work with the server.

components \ chatix \ ChatixSDK.js







We import ChatixCore in it.







 import ChatixCore from 'chatix-core';
      
      





In the ChatixSDK component , create an instance of the ChatixCore class and pass websiteId as an argument.







 const websiteId = "_WEBSITE_ID"; this.sdk = new ChatixCore(websiteId);
      
      





Now in this.sdk you can use the methods for working with the chat room. See the list of methods on the chatix-core project page







Next, we need to connect to the server and get data about the chat room created earlier. There are asynchronous start () and getChatroom () methods for this.







After you receive the chat room object, let's immediately take its name and transfer it to the App . To do this, add the callback function updateChatroomTitle(chatroom.title)



in the App and call it in the ChatixSDK .







ChatixSDK







 class ChatixSDK extends React.Component { constructor(props){ super(props); const websiteId = "_WEBSITE_ID"; this.chatroomId = "_CHATROOM_ID"; this.sdk = new ChatixCore(websiteId); this.sdk.start() .then( async () => { try { // refresh information about chatroom and call passed handler const chatroom = await this.sdk.getChatroom(this.chatroomId); if (props.updateChatroomTitle) { props.updateChatroomTitle(chatroom.title); } } catch (err) { console.error(err); } }) .catch((e) => { console.error(e); }); } render(){ return null; } }
      
      





you can look at this.chatroomId



in the manager interface by opening the desired chat room.







ChatroomID







Now in the App we’ll connect the ChatixSDK component and throw the updateChatroomTitle function into it, which will update the chat name. We also add a ref



link to it so that we can access this component.







The app







 this.chatixSDK = React.createRef();
      
      





 setChatroomTitle = (newName) => { const newStateFragment = { chatroomName: newName}; this.setState({...this.state, ...newStateFragment}); };
      
      





The app







 render() { return ( <div className="App"> <Header chatroomName={this.state.chatroomName} me={this.state.me} updateVisitor={this.onUpdateVisitor} /> <Main members={this.state.members} messages={this.state.messages} onSendNewMessage={this.onSendNewMessage} me={this.state.me} /> <ChatixSDK ref={this.chatixSDK} updateChatroomTitle={this.setChatroomTitle} /> </div> ); };
      
      





Done. Now, immediately after connecting to the server, we request chat data, get its name and write it to the state of the App component, and since changes in the state cause the component to be rendered again, the name in the header will be updated automatically. Now the default name in the state can be replaced with an empty string.







The app







 chatroomName: ''
      
      





Now let's fill the sidebar with real users.

But before you get the list of users, you must connect to the chat, for this, in the ChatixSDK inside the this.sdk.start()



function, we get a list of all the user's chat rooms, check if it is connected to the current one and if not, then connect it.







ChatixSDK







 const myChatrooms = await this.sdk.getMyChatrooms(); if (myChatrooms.filter(x => x.id===this.chatroomId).length === 0) { await this.sdk.connectToChatroom(this.chatroomId); }
      
      





After we make sure that our user is connected to the chat room, we can get a list of participants in this chat.







ChatixSDK







 // lets get all chatroom members using infinite loop with break on empty server response let membersPage = 1; let allChatroomMembers = []; while(true) { let pagedMembers = await this.sdk.getChatroomMembers(this.chatroomId, membersPage++, 10); allChatroomMembers = [...allChatroomMembers, ...pagedMembers]; if (pagedMembers.length === 0) { break; } }
      
      





Here, in an infinite loop, we request users page by page until we get everyone, as soon as we get everyone, we break the loop. After that, just like the name of the chat room, we forward it to the parent component using the callback function.







ChatixSDK







 if (props.setChatroomMembers) { props.setChatroomMembers(allChatroomMembers); }
      
      





Now, in the App component, create this callback function setChatroomMembers



which will sort users by their online / offline status and alphabetically and write them to state.







App.js







 setChatroomMembers = (members) => { members.sort(this.sortMembers); const newStateFragment = { members: members}; this.setState({...this.state, ...newStateFragment}); }
      
      





Add the sorting function sortMembers . It sorts users by status and alphabetically.







App.js







 sortMembers(a, b) { if (a.is_online === true && b.is_online === false) { return -1; } else if (b.is_online === true && a.is_online === false) { return 1; } else { if (a.name && b.name) { if (a.name.toLocaleUpperCase() > b.name.toLocaleUpperCase()) { return 1; } else if (a.name.toLocaleUpperCase() < b.name.toLocaleUpperCase()) { return -1; } } else if (a.name && !b.name) { return -1; } else if (!a.name && b.name) { return 1; } if (a.uuid > b.uuid) { return -1; } else { return 1; } } }
      
      





Next, we forward the setChatroomMembers function in the ChatixSDK .







The app







 render() { return ( <div className="App"> <Header chatroomName={this.state.chatroomName} me={this.state.me} updateVisitor={this.onUpdateVisitor} /> <Main members={this.state.members} messages={this.state.messages} onSendNewMessage={this.onSendNewMessage} me={this.state.me} /> <ChatixSDK ref={this.chatixSDK} updateChatroomTitle={this.setChatroomTitle} setChatroomMembers={this.setChatroomMembers} /> </div> ); };
      
      





Now, immediately after connecting to the server, we, as well as with the header, request a list of all connected users and write it to the state of the App component. And also change the default value of the user list in the state.







The app







 members: []
      
      





Now, by exactly the same principle, we get the object of the current user and an array of messages and also write them to the App state







ChatixSDK







 // lets load 100 last messages from current chatroom const lastMessages = await this.sdk.getChatroomMessages(this.chatroomId, null, 100); if (props.setChatroomMessages) { props.setChatroomMessages(lastMessages); } if (props.setMe) { const me = this.sdk.getVisitor(); this.props.setMe(me); }
      
      





The app







 <ChatixSDK ref={this.chatixSDK} setMe={this.setMe} updateChatroomTitle={this.setChatroomTitle} setChatroomMembers={this.setChatroomMembers} setChatroomMessages={this.setChatroomMessages} />
      
      





Next, we will send messages.







We already have the onSendNewMessage



function in the App that displays the message we send to the console. Instead, we will simply call the sendChatroomMessage



method to send the message from the ChatixSDK .

This is an asynchronous method and it returns the object of the sent message in the response, which we immediately add to the message array in the state. By the way, please note that we are this.chatixSDK



link created earlier.







The app







 onSendNewMessage = async (message) => { let receivedMsg = await this.chatixSDK.current.sendChatroomMessage(message); const currentMessages = this.state.messages; currentMessages.push(receivedMsg); const newStateFragment = {messages: currentMessages}; this.setState({...this.state, ...newStateFragment}); }
      
      





Since a change in the state causes it to be re-rendered, the list of messages will be updated automatically. But we need to make sure that when adding messages, the scroll in the message block goes down.







To do this, open the MessageContainer component and using the useEffect hook , monitor the message array for changes, and as soon as it has changed and the messages are added, we get a scrollHeight block with messages and scroll it by the same amount







 function MessageContainer(props) { const messagesContainer = React.createRef(); useEffect(() => { messagesContainer.current.scrollTop = messagesContainer.current.scrollHeight }, [props, messagesContainer]); const messageList = props.messages.map(message => <Message key={message.uuid} sender={props.members.find((member) => member.uuid === message.sender_id)} message={message} /> ); return ( <section className="MessageContainer" ref={messagesContainer}> {messageList} </section> ); }
      
      





Now let's finish updating the username. We have already created an input in the header and when changing it, we forward the updated user object to the App component and there we display it in the console. Let's finish this function. To do this, add the method call this.chatixSDK.current.updateVisitor(user)



to it, this will update the data on the server. And just update the data in the local state, for this we update the this.state.me



object and in the this.state.members



array this.state.members



also find the current user and update him. This is necessary to update the name of the current user in the messages sent to them.







The app







 onUpdateVisitor = (user) => { this.chatixSDK.current.updateVisitor(user) this.setMe(user) let currentUser = this.state.members.find((member) => (member.uuid === user.uuid)) let currentUserIndex = this.state.members.indexOf(currentUser) let newMembers = [...this.state.members] newMembers[currentUserIndex] = user; this.setState({ members: newMembers }) }
      
      





Now we need to learn how to respond to incoming messages, connecting / disconnecting users and changing information and connected users.







To do this, in the ChatixSDK.js file in the constructor, we need to override the callback functions. You can see the full list of functions and arguments on the chatix-core project page.







We are currently interested in onChatroomMessageReceived , onMemberConnectedToChatroom , onMemberDisconnectedFromChatroom and onApplyVisitorInfo .







We redefine them and for each function we call our callback which we will create in the App .







 this.sdk.onChatroomMessageReceived = (chatroomId, message) => { if (chatroomId === this.chatroomId) { this.props.onNewMessageReceived(message); } }; this.sdk.onMemberConnectedToChatroom = (chatroomId, member) => { if (chatroomId === this.chatroomId && props.addChatroomMember) { this.props.addChatroomMember(member); } }; this.sdk.onMemberDisconnectedFromChatroom = (chatroomId, member) => { if (chatroomId === this.chatroomId && props.removeChatroomMember) { this.props.removeChatroomMember(member); } }; this.sdk.onApplyVisitorInfo = (visitor) => { this.props.onMemberUpdated(visitor) }
      
      





Next, go to the App and create these functions.







onNewMessageReceived (message)

This function takes a message object and simply adds it to state with the rest of the messages. After that, the component will be rendered again and it will be displayed in the list, just like when sending an outgoing message.







The app







 onNewMessageReceived = (message) => { const currentMessages = this.state.messages; currentMessages.push(message); const newStateFragment = {messages: currentMessages}; this.setState({...this.state, ...newStateFragment}); }
      
      





App

addChatroomMember(member)

state members. .







App







 addChatroomMember = (member) => { const newStateFragment = {}; const currentMembers = this.state.members; currentMembers.push(member); currentMembers.sort(this.sortMembers); newStateFragment.members = currentMembers; this.setState({...this.state, ...newStateFragment}); }
      
      





App

removeChatroomMember(memberId)

state members state .







 removeChatroomMember = (memberId) => { const currentMembers = this.state.members; const filteredMembers = currentMembers.filter(x=> x.uuid !== memberId); const newStateFragment = {members: filteredMembers}; this.setState({...this.state, ...newStateFragment}); }
      
      





onMemberUpdated(updatedMember)

. . state .







App







 onMemberUpdated = (updatedMember) => { let oldMember = this.state.members.find(member => member.uuid === updatedMember.uuid); oldMember = this.state.members.indexOf(oldMember); let newStateMembers = this.state.members; newStateMembers[oldMember] = updatedMember; this.setState({ members: newStateMembers }) }
      
      





ChatixSDK







ChatixSDK







 <ChatixSDK ref={this.chatixSDK} setMe={this.setMe} updateChatroomTitle={this.setChatroomTitle} setChatroomMembers={this.setChatroomMembers} addChatroomMember={this.addChatroomMember} removeChatroomMember={this.removeChatroomMember} setChatroomMessages={this.setChatroomMessages} onNewMessageReceived={this.onNewMessageReceived} onMemberUpdated={this.onMemberUpdated} />
      
      





Done! \ , , / .







alekseyso

Additional materials:

SDK Chatix ()

SDK Chatix (npm)

192 -








All Articles