Chat app using React.js + ASP.NET SignalR Core (Part 1)

Introduction

In this article we are going to learn how to make a real time chat application using React.js and ASP.NET SignalR Core

Features

Our chat application will have the following features:

  • Login system with username and email
  • Public chat room for all connected users
  • Peer to peer or one-to-one chat between 2 users
  • Online/Offline status
  • Emoji parsing from text

Why React.js and ASP.NET Signalr Core ?

I chose React.js because it is one of the best front end development library available which is open source. React in 2019 has become a huge library, now also porting to mobile as react-native and react-desktop.

We chose ASP.NET Signalr Core because it is also one of the best back end framework. Just like React, it is also an open source & cross platform framework.

Making Chat UI with React.js

So first things first. We are going to make our chat application UI. Let’s analyze, what flow do we have here in starting?

  1. A login screen which asks users for username/password to login.
  2. A chat lobby with all connected users.

So we’re going to use Bootstrap for the layouts. I made two components for starting ChatScreen.js and LoginScreen.js and included them in App.js like this:

import React from 'react';
import './App.css';
import BSCSS from 'bootstrap/dist/css/bootstrap.min.css'
import LoginScreen from './LoginScreen';
import ChatScreen from './ChatScreen';

class App extends React.Component {

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

    render() {

        if (this.state.username.length === 0) {
            return (<LoginScreen></LoginScreen>);
        } else {
            return (<ChatScreen></ChatScreen>);
        }
    }

}

export default App;

We’re importing both components in App.js and showing them conditionally. For now, if username is not empty, we show the ChatScreen otherwise we’ll show the login form.

Login Form (LoginScreen.js) component

A very basic login form using Bootstrap. We’ll use this to login to our API and move to chat lobby.

import React from 'react';
import Logo from './logo.png';
import {Container, Row, Col, Form, Button} from "react-bootstrap";

class LoginScreen extends React.Component {

    render() {
        return (
            <Container>
                <Row className={'justify-content-md-center'} style={{marginTop: '7em'}}>
                    <Col lg={4} className={'login-box'}>
                        <div>
                            <img src={Logo} alt="" className={'logo-login'}/>
                            <h3 className={'text-center'}>Chat App</h3>
                        </div>
                        <Form.Group>
                            <Form.Label>Username</Form.Label>
                            <Form.Control type="text" placeholder="Type your name here"/>
                        </Form.Group>
                        <Form.Group>
                            <Form.Label>Password</Form.Label>
                            <Form.Control type="text" placeholder="Your password"/>
                        </Form.Group>
                        <Form.Group>
                            <Row className={'justify-content-md-center'} style={{marginTop: '2em'}}>
                                <Col lg={4}>
                                    <Button variant={'primary'} type={'submit'} size={'md'} block>
                                        Login
                                    </Button>
                                </Col>
                            </Row>
                        </Form.Group>
                    </Col>
                </Row>
            </Container>
        );
    }

}

export default LoginScreen;

Chat screen (ChatScreen.js) component.

This is our main component where all logic will reside. For now its just a simple layout. Later when we develop the SignalR Hub and API we will develop it further to add more functionality.

import React from 'react';
import {Form, ListGroup, Container, Button, Row, Col} from 'react-bootstrap'

class ChatScreen extends React.Component {

    lastMessage = null;

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

    ScrollToBottom() {

        if (this.lastMessage)
            this.lastMessage.scrollIntoView();

    }

    componentDidMount() {

        // Adding demo data, we'll make it dynamic later :)
        
        var users = ['Superman', 'Batman', 'Flash', 'Spoderman', 'Iron man 3', 'Wonder woman', 'Hulk', 'John Cena'];

        var messages = [
            {author: 'Superman', message: 'Hey, whats up?'},
            {author: 'me', message: 'Nothing, just writing my a react + asp.net chat tutorials'},
            {author: 'Superman', message: 'Wow!'},
            {author: 'Superman', message: 'Sounds great! When can I see?'},
            {author: 'me', message: 'Coming soon!'},
            {author: 'Superman', message: "Can't wait :o"},
        ];

        this.setState({
            messages: messages,
            users: users
        }, () => {
            this.ScrollToBottom();
        });

    }

    render() {
        return (
            <Container style={{marginTop: 30, backgroundColor: '#fff', padding: '1em',}}>
                <Row>
                    <Col>
                        <div className={'text-right'}>
                            <small>Logged in as Waleed. <a href="#">Logout</a></small>
                        </div>
                    </Col>
                </Row>
                <Row className="justify-content-md-center">
                    <Col lg="3">
                        <div style={{maxHeight: '500px',}}>
                            <ListGroup style={{cursor: 'pointer'}}>
                                {
                                    this.state.users.map((u, index) => {

                                            var bgColor = (index === 0) ? '#7187ee' : '';
                                            var textColor = (index === 0) ? '#fff' : '';

                                            return (
                                                <ListGroup.Item style={{'backgroundColor': bgColor, 'color': textColor}}
                                                                as={'li'}>{u}</ListGroup.Item>
                                            );

                                        }
                                    )
                                }
                            </ListGroup>
                        </div>
                    </Col>
                    <Col lg="9">
                        <Row>
                            <Col lg={12}>
                                <div className={'message-author'}>
                                    <h3>Superman</h3>
                                </div>
                            </Col>
                        </Row>
                        <div style={{height: '450px', overflowY: 'scroll', overflowX: 'hidden'}}>
                            <Row>
                                <Col lg={12}>
                                    {
                                        this.state.messages.map((msg, index) => {

                                            var classNames = (msg.author === 'me') ? 'message-left' : 'message-right';

                                            return (
                                                <div key={'el' + index} className={'message'}
                                                     ref={(el) => this.lastMessage = el}>
                                                    <div
                                                        className={'message-body ' + classNames}>
                                                        {msg.message}
                                                    </div>
                                                </div>
                                            );

                                        })
                                    }
                                </Col>
                            </Row>
                        </div>
                    </Col>
                </Row>
                <Row>
                    <Col lg={3}/>
                    <Col>
                        <div style={{marginTop: '1em'}}>
                            <Form.Control onKeyUp={(event) => {

                                if (event.keyCode === 13 && event.target.value.trim() !== '') {

                                    this.setState({
                                        messages: this.state.messages.concat({
                                            'author': 'me',
                                            'message': event.target.value
                                        }),
                                    }, () => {
                                        this.ScrollToBottom();
                                    });

                                    event.target.value = '';

                                }

                            }} type="text" placeholder="Type your message here"/>
                        </div>
                    </Col>
                </Row>
            </Container>
        );
    }
}

export default ChatScreen;

Whats happening here?

In componentDidMount() I am simply adding some demo data to show on screen.

We have ScrollToBottom() function which simply brings the last message into the view. We’ll evolve it later if needed. In render function, while looping all the messages to show. We are setting this.lastMessage to the last message div which was added so we can later scroll to it.

On form control, we have a keyUp event which simply checks if string length is not zero and key was Enter key (keyCode 13 == Enter). We enter the text in the messages array and update our state.

App.css

For now, this app.css file is really simple. We’ll evolve it later, so keep track of it on the repository.

.logo-login {
    display: block;
    margin: 0 auto;
    margin-top: -60px;
    margin-bottom: -60px;
}

body {
    background: #141414 !important;
}

.message {
    overflow: hidden;
    display: block;
}

.message-body {
    padding: 10px;
    margin: 0.5em;
    margin-bottom: 0;
    border-radius: 1em;
    display: inline-block;
}

.message-left {
    float: left;
    background: rgb(177, 200, 172);
    background: linear-gradient(90deg, rgba(177, 200, 172, 1) 0%, rgb(91, 186, 255) 0%, rgb(5, 255, 59) 100%);
    color: #fff;
}

.message-right {
    float: right;
    background: #eee;
    color: #000;
}

.message-author {

}

.login-box {
    background: #ffffff;
    border: 1px solid #ccc;
    padding: 1em;
    -webkit-border-radius: 0.5em;
    -moz-border-radius: 0.5em;
    border-radius: 0.5em;
}

Code available at React Chat App Using SignalR repository

Next part: SignalR tutorial for chat server Coming Soon!

1 Reply to “Chat app using React.js + ASP.NET SignalR Core (Part 1)”

Leave a Reply

Your email address will not be published. Required fields are marked *