Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Creating a Chat Application Using React and ASP.NET Core - Part 2

DZone's Guide to

Creating a Chat Application Using React and ASP.NET Core - Part 2

In Part 2 of this five-part series, we get the UI of our chat application working properly using React and some tricky TypeScript code.

· Web Dev Zone ·
Free Resource

Jumpstart your Angular applications with Indigo.Design, a unified platform for visual design, UX prototyping, code generation, and app development.

In this blog series, I'm going to create a small chat application using React and ASP.NET Core, to learn more about React and to learn how React behaves in an ASP.NET Core project during development and deployment. This Series is divided into 5 parts, which should cover all relevant topics:

  1. React Chat Part 1: Requirements & Setup
  2. React Chat Part 2: Creating the UI & React Components
  3. React Chat Part 3: Adding Websockets using SignalR
  4. React Chat Part 4: Authentication & Storage
  5. React Chat Part 5: Deployment to Azure

I also set-up a GitHub repository where you can follow the project. Feel free to share your ideas about that topic in the comments below or in issues on GitHub. Because I'm still learning React, please tell me about significant and conceptual errors by dropping a comment or by creating an Issue on GitHub. Thanks.

Basic Layout

First, let's have a quick look at the hierarchy of the React components in the folder ClientApp.

The app gets bootstrapped within the boot.tsx file. This is the first sort of component where the AppContainer gets created and the router is placed. This file also contains the call to render the React app in the relevant HTML element, which is a div with the ID react-app in this case. It is a div in the Views/Home/Index.cshtml

This component also renders the content of the routes.tsx. This file contains the route definitions wrapped inside a Layout element. This Layout element is defined in the layout.tsx inside the components folder. The routes.tsx also references three more components out of the components folder: Home, Counter, and FetchData. So it seems the router renders the specific components, depending on the requested path inside the Layout element:

// routes.tsx
import * as React from 'react';
import { Route } from 'react-router-dom';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';

export const routes = <Layout>
    <Route exact path='/' component={ Home } />
    <Route path='/counter' component={ Counter } />
    <Route path='/fetchdata' component={ FetchData } />
</Layout>;

As expected, the Layout component defines the basic layout and renders the contents into a Bootstrap grid column element. I changed that a little bit to render the contents directly into the fluid container and the menu is now outside the fluid container. This component now contains less code than before.:

import * as React from 'react';
import { NavMenu } from './NavMenu';

export interface LayoutProps {
    children?: React.ReactNode;
}

export class Layout extends React.Component<LayoutProps, {}> {
    public render() {
        return <div>
            <NavMenu />
            <div className='container-fluid'>
                {this.props.children}
            </div>
        </div>;
    }
}

I also changed the NavMenu component to place the menu on top of the page using the typical Bootstrap styles (Visit the repository for more details).

My chat goes into the Home component, because this is the most important feature of my app. This is why I removed all the contents of the Home component and placed the layout for the actual chat there.

import * as React from 'react';
import { RouteComponentProps } from 'react-router';

import { Chat } from './home/Chat';
import { Users } from './home/Users';

export class Home extends React.Component<RouteComponentProps<{}>, {}> {
    public render() {
        return <div className='row'>
            <div className='col-sm-3'>
              <Users />
            </div>
            <div className='col-sm-9'>
                <Chat />
            </div>
        </div>;
    }
}

This component uses two new components: Users to display the online users and Chat to add the main chat functionalities. It seems to be a common way in React to store sub-components inside a subfolder with the same name as the parent component. So, I created a Home folder inside the components folder and placed the Users component and the Chat component inside of that new folder.

The Users Component

Let's have a look at the simpler Users component first. This component doesn't have any interaction yet. It only fetches and displays the users online. To keep the first snippet simple I removed the methods inside. This file imports everything from the module 'react' as a React object. Using this we are able to access the Component type from which we need to derive:

// components/Home/Users.tsx
import * as React from 'react';

interface UsersState {
    users: User[];
}
interface User {
    id: number;
    name: string;
}

export class Users extends React.Component<{}, UsersState> {
    //
}

This base class also defines a state property. The type of that state is defined in the second generic argument of the React.Component base class (the first generic argument is not needed here). The state is a kind of a container type that contains data you want to store inside the component. In this case, I just need a UsersState with a list of users inside. To display a user in the list we only need an identifier and a name. A unique key or id is required by React to create a list of items in the DOM

I don't fetch the data from the server side yet. This post is only about the UI components, so I'm going to mock the data in the constructor:

constructor() {
    super();
    this.state = {
        users: [
            { id: 1, name: 'juergen' },
            { id: 3, name: 'marion' },
            { id: 2, name: 'peter' },
            { id: 4, name: 'mo' }]
    };
}

Now the list of users is available in the current state and I'm able to use this list to render the users:

public render() {
    return <div className='panel panel-default'>
        <div className='panel-body'>
            <h3>Users online:</h3>
            <ul className='chat-users'>
                {this.state.users.map(user =>
                    <li key={user.id}>{user.name}</li>
                )}
            </ul>
        </div>
    </div>;
}

JSX is a weird thing: HTML like XML syntax, completely mixed with JavaScript (or TypeScript in this case) but it works. It remembers a little bit like Razor. this.state.users.map iterates through the users and renders a list of items per user.

The Chat Component

The Chat component is similar but contains more details and some logic to interact with the user. Initially, we have almost the same structure:

// components/Home/chat.tsx
import * as React from 'react';
import * as moment from 'moment';

interface ChatState {
    messages: ChatMessage[];
    currentMessage: string;
}
interface ChatMessage {
    id: number;
    date: Date;
    message: string;
    sender: string;
}

export class Chat extends React.Component<{}, ChatState> {
    //
}

I also imported the module moment, which is in the moment.js library I installed using NPM:

npm install moment --save 

moment.js is a pretty useful library to easily work with dates and times in JavaScript. It has a ton of features, like formatting dates, displaying times, creating relative time expressions, and it also provides a proper localization of dates.

Now it makes sense to have a look into the render method first:

// components/Home/chat.tsx
public render() {
    return <div className='panel panel-default'>
        <div className='panel-body panel-chat'
            ref={this.handlePanelRef}>
            <ul>
                {this.state.messages.map(message =>
                    <li key={message.id}><strong>{message.sender} </strong>
                        ({moment(message.date).format('HH:mm:ss')})<br />
                        {message.message}</li>
                )}
            </ul>
        </div>
        <div className='panel-footer'>
            <form className='form-inline' onSubmit={this.onSubmit}>
                <label className='sr-only' htmlFor='msg'>Message</label>
                <div className='input-group col-md-12'>
                    <button className='chat-button input-group-addon'>:-)</button>
                    <input type='text' value={this.state.currentMessage}
                        onChange={this.handleMessageChange}
                        className='form-control'
                        id='msg'
                        placeholder='Your message'
                        ref={this.handleMessageRef} />
                    <button className='chat-button input-group-addon'>Send</button>
                </div>
            </form>
        </div>
    </div>;
}

I defined a Bootstrap panel, that has the chat area in the panel-body and the input fields in the panel-footer. In the chat area, we also have an unordered list and the code to iterate through the messages. This is almost similar to the user list. We only display some more dates here. Here you can see the usage of moment.js to easily format the message date.

The panel-footer contains the form to compose the message. I used an input group to add a button in front of the input field and another one after that field. The first button is used to select an emoji. The second one is to also send the message (for people who cannot use the enter key to submit the message).

The ref attributes are used for a cool feature. Using this, you are able to get an instance of the element in the backing code. This is nice to work with instances of elements directly. We will see the usage later on. The code in the ref attributes are pointing to methods that get an instance of that element passed in:

msg: HTMLInputElement;
panel: HTMLDivElement;

// ...

handlePanelRef(div: HTMLDivElement) {
    this.panel = div;
}
handleMessageRef(input: HTMLInputElement) {
    this.msg = input;
}

I save the instance globally in the class. One thing I didn't expect was the weird behavior of this. This behavior is a typical JavaScript behavior, but I expected is to be solved in TypeScript. I also didn't see this in Angular. The keyword this is not set. It is nothing. If you want to access this in methods used by the DOM, you need to kinda 'inject' or 'bind' an instance of the current object to get this set. This is typical for JavaScript and makes absolute sense. This needs to be done in the constructor:

constructor() {
    super();
    this.state = { messages: [], currentMessage: '' };

    this.handlePanelRef = this.handlePanelRef.bind(this);
    this.handleMessageRef = this.handleMessageRef.bind(this);
    // ...
}

This is the current constructor, including the initialization of the state. As you can see, we bind the current instance to those methods. We need to do this for all methods, that need to use the current instance.

To get the message text from the text field, it is needed to bind an onChange method. This method collects the value from the event target:

handleMessageChange(event: any) {
    this.setState({ currentMessage: event.target.value });
}

Don't forget to bind the current instance in the constructor:

this.handleMessageChange = this.handleMessageChange.bind(this); 

With this code, we get the current message into the state to use it later on. The current state is also bound to the value of that text field, just to clear this field after submitting that form.

The next important event is onSubmit in the form. This event gets triggered by pressing the send button or by pressing enter inside the text field:

onSubmit(event: any) {
    event.preventDefault();
    this.addMessage();
}

This method stops the default behavior of HTML forms, to avoid a reload of the entire page. And calls the method addMessage, that creates and ads the message to the current states messages list:

addMessage() {
    let currentMessage = this.state.currentMessage;
    if (currentMessage.length === 0) {
        return;
    }
    let id = this.state.messages.length;
    let date = new Date();

    let messages = this.state.messages;
    messages.push({
        id: id,
        date: date,
        message: currentMessage,
        sender: 'juergen'
    })
    this.setState({
        messages: messages,
        currentMessage: ''
    });
    this.msg.focus();
    this.panel.scrollTop = this.panel.scrollHeight - this.panel.clientHeight;
}

Currently, the id and the sender of the message are faked. Later on, in the next post, we'll send the message to the server using Websockets and we'll get a message including a valid id back. We'll also have an authenticated user later on. As mentioned in the current post, we're just about to get the UI running.

We get the currentMessage and the messages list out of the current state. Then we add the new message to the current list and assign a new state, with the updated list and an empty currentMessage. Setting the state triggers an event to update the UI. If I just update the fields inside the state, the UI doesn't get notified. It is also possible to only update a single property of the state.

If the state is updated, I need to focus the text field and to scroll the panel down to the latest message. This is the only reason why I need the instance of the elements and why I used the ref methods.

That's it!

After pressing F5, I see the working chat UI in the browser

Closing Words

So, we've got basic UI working. This was easier than expected, though I did get a little stuck when accessing the HTML elements to focus the text field and to scroll the chat area and when I tried to access the current instance using this. React is heavily used and the React community is huge. This is why it is easy to get help pretty fast.

In the next post, I'm going to integrate SignalR and to get the Websockets running. I'll also add two Web APIs to fetch the initial data. The current logged on users and the latest 50 chat messages don't need to be pushed by the Websocket. Using this, I need to get into the first functional component in React and to inject this into the UI components of this post.

Take a look at the Indigo.Design sample applications to learn more about how apps are created with design to code software.

Topics:
web dev ,react.js development ,web application development ,asp.net core

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}