Getting Started with Kuzzle and ReactJS

This tutorial will help you get started with Kuzzle V2 (+ Javascript SDK 7) and ReactJS. We will create documents in Kuzzle and subscribe to document notifications to develop a realtime chat.

Requirements

Prepare your environment

Create your React app and install all the dependencies from the command line using npm:

Copied to clipboard!
npx create-react-app kuzzle-playground

Install Kuzzle's Javascript SDK:

Copied to clipboard!
cd kuzzle-playground
npm install kuzzle-sdk@7

We'll rewrite the src/App.js so you can remove everything inside.

Instantiating Kuzzle SDK

We have to connect the server so that our client can interact with it. To achieve this, create a src/services/kuzzle.js file to put our kuzzle instance declaration:

Copied to clipboard!
import { Kuzzle, WebSocket } from 'kuzzle-sdk';

export default new Kuzzle(new WebSocket('localhost'));

You can now edit the src/App.js file to connect to Kuzzle. To do this, add the following imports at the top of the file:

Copied to clipboard!
import React from 'react';
import kuzzle from './services/kuzzle';
import './App.css';

Now, define the App class. The constructor must add a message property in the state (we'll use it to store the user input message) a username property, messages property (we’ll use it to store all messages) and a validate property (Value that will change the display):

Copied to clipboard!
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      username: "",
      message: "",
      messages: [],
      validate: false,
    };
  }

Then we will establish the connection to kuzzle and create the index and collection of our chat if they don't exist. Once connection is established, we need to ensure our Kuzzle instance is properly initialized before we allow the user to interact with it. We implement this logic in the the following valid() method in the App Component:

Copied to clipboard!
valid = async () => {
  await kuzzle.connect();
  if (!await kuzzle.index.exists("chat")) {
    await kuzzle.index.create("chat");
    await kuzzle.collection.create("chat", "messages");
  }
  await this.subscribeMessages();
  await this.fetchMessages();
  this.setState({ validate: true });
  }

Get the username

First of all, we need to know the user's name, this function will return the input allowing the user to enter his or her name. Let's add this one.

Copied to clipboard!
renderUsernameInput = () => {
  if (this.state.validate === false) {
    return (
      <div className="wrapper">
        <input autoFocus name="username" id="username"
          onKeyUp={this.handleInputChange}
          type="text"
          placeholder="Enter your nickname"
        />
        <button onClick={this.valid}>Valid</button>
      </div>
    );
  }
  }

As you can see we'll need the handleInputChange() function to save the username variable

Copied to clipboard!
handleInputChange = event => {
  const { value, name } = event.target;
  this.setState({
    [name]: value
  });
  }

Display the messages

Then, create the following functions to fetch and display the messages:

Copied to clipboard!
getMessage = document => {
  const message = {
    _id: document._id,
    value: document._source.value,
    createdAt: document._source._kuzzle_info.createdAt,
    username: document._source.username
  };
  return message;
  }

The fetchMessage() function will search for the first hundred newest messages and store them in our array. We called it in the valid() function we created above.

Copied to clipboard!
fetchMessages = async () => {
  const results = await kuzzle.document.search(
    'chat',
    'messages',
    {
      sort: {
        '_kuzzle_info.createdAt': 'desc'
      }
    },
    {
      size: 100
    }
  );
  let messages = results.hits.map(hit => this.getMessage(hit));
  this.setState({ messages })
  }

Now, add the following function outside the App component to display the messages:

Copied to clipboard!
const Message = function (props) {
  const { message, username } = props;
  return (
    <div className={(message.username === username ? 'fromMe' : 'fromOthers') + ' messages'}>
      <span> User: <b>{message.username}</b>  (</span>
      <span> {new Date(message.createdAt).toLocaleString().split("GMT")[0]} )</span>
      <p> {message.value} </p>
    </div>
  );
}

Send messages

We need to write a simple method that will create a new message document in Kuzzle.

Copied to clipboard!
sendMessage = async () => {
  if (this.state.message === '') return;
  await kuzzle.document.create(
    'chat',
    'messages',
    {
      value: this.state.message,
      username: this.state.username
    }
  );
  this.setState({ message: '' })
  }

We will receive real-time notifications from Kuzzle each time a message is added to our message collection. We will use those notifications to append the messages to our application state.

Now, we need to subscribe to the collection that contains our messages. So let's create our subscribeMessages() method. It will call Kuzzle's realtime controller to allow us to receive notifications on message creations:

Copied to clipboard!
subscribeMessages = () => {
  return (
    kuzzle.realtime.subscribe(
      'chat',
      'messages',
      {},
      notification => {
        if (notification.type !== 'document') return;
        if (notification.action !== 'create') return;
        this.setState({
          messages: [
            this.getMessage(notification.result),
            ...this.state.messages
          ]
        })
      }
    )
  );
  }

Add an input field bound to the message property, and a button which calls our sendMessage() function:

Copied to clipboard!
renderMessageInput = () => {
  if (this.state.validate === true) {
    return (
      <div className="wrapper">
        <input autoFocus type="text"
          id="message"
          name="message"
          onChange={this.handleInputChange}
          placeholder="Enter your message"
          value={this.state.message}
        />
        <button onClick={() => this.sendMessage()}>Send</button>
      </div>
    );
  }
  }

Add the following CSS classes in the src/App.css file:

Copied to clipboard!
.wrapper {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 15px;
}

.messages {
  padding: 10px;
  margin: 10px;
  width: 45vw;
  border-radius: 10px;
}

.fromMe {
  text-align: right;
  float: right;
  margin-left: 49vw;
  background-color: rgb(0, 40, 53, 0.3);
  ;
}

.fromOthers {
  text-align: left;
  margin-right: 49vw;
  float: left;
  color: #EEEFFF;
  background-color: rgb(0, 40, 53, 0.9);
  ;
}

Finally, add the render() method of our App component:

Copied to clipboard!
render() {
  return (
    <div>
      {this.renderUsernameInput()}
      {this.renderMessageInput()}
      <div>
        {this.state.messages.map((message, idx) => {
          return (<Message key={idx} message={message} username={this.state.username} />)
        })}
      </div>
    </div>
  );
  }

The entire file should look like this:

Copied to clipboard!
import React from 'react';
import kuzzle from './services/kuzzle';
import './App.css';
const Message = function (props) {
  const { message, username } = props;
  return (
    <div className={(message.username === username ? 'fromMe' : 'fromOthers') + ' messages'}>
      <span> User: <b>{message.username}</b>  (</span>
      <span> {new Date(message.createdAt).toLocaleString().split("GMT")[0]} )</span>
      <p> {message.value} </p>
    </div>
  );
}
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      username: "",
      message: "",
      messages: [],
      validate: false,
    };
  }
  valid = async () => {
    await kuzzle.connect();
    if (!await kuzzle.index.exists("chat")) {
      await kuzzle.index.create("chat");
      await kuzzle.collection.create("chat", "messages");
    }
    await this.subscribeMessages();
    await this.fetchMessages();
    this.setState({ validate: true });
  }
  subscribeMessages = () => {
    return (
      kuzzle.realtime.subscribe(
        'chat',
        'messages',
        {},
        notification => {
          if (notification.type !== 'document') return;
          if (notification.action !== 'create') return;
          this.setState({
            messages: [
              this.getMessage(notification.result),
              ...this.state.messages
            ]
          })
        }
      )
    );
  }
  getMessage = document => {
    const message = {
      _id: document._id,
      value: document._source.value,
      createdAt: document._source._kuzzle_info.createdAt,
      username: document._source.username
    };
    return message;
  }
  fetchMessages = async () => {
    const results = await kuzzle.document.search(
      'chat',
      'messages',
      {
        sort: {
          '_kuzzle_info.createdAt': 'desc'
        }
      },
      {
        size: 100
      }
    );
    let messages = results.hits.map(hit => this.getMessage(hit));
    this.setState({ messages })
  }
  handleInputChange = event => {
    const { value, name } = event.target;
    this.setState({
      [name]: value
    });
  }

  renderUsernameInput = () => {
    if (this.state.validate === false) {
      return (
        <div className="wrapper">
          <input autoFocus name="username" id="username"
            onKeyUp={this.handleInputChange}
            type="text"
            placeholder="Enter your nickname"
          />
          <button onClick={this.valid}>Valid</button>
        </div>
      );
    }
  }
  sendMessage = async () => {
    if (this.state.message === '') return;
    await kuzzle.document.create(
      'chat',
      'messages',
      {
        value: this.state.message,
        username: this.state.username
      }
    );
    this.setState({ message: '' })
  }
  renderMessageInput = () => {
    if (this.state.validate === true) {
      return (
        <div className="wrapper">
          <input autoFocus type="text"
            id="message"
            name="message"
            onChange={this.handleInputChange}
            placeholder="Enter your message"
            value={this.state.message}
          />
          <button onClick={() => this.sendMessage()}>Send</button>
        </div>
      );
    }
  }
  render() {
    return (
      <div>
        {this.renderUsernameInput()}
        {this.renderMessageInput()}
        <div>
          {this.state.messages.map((message, idx) => {
            return (<Message key={idx} message={message} username={this.state.username} />)
          })}
        </div>
      </div>
    );
  }
}

export default App;

To launch this app, just type the following command:

Copied to clipboard!
npm start

You can now send new messages to Kuzzle and receive the notifications of messages creation to update your state and display the new messages.

Going further

Now that you are more familiar with Kuzzle, dive even deeper to learn how to leverage its full capabilities: