SDK
SDK Javascript v6.x
1

You are currently looking at the documentation of a previous version of Kuzzle. We strongly recommend that you use the latest version. You can also use the version selector in the top menu.

Getting Started with Kuzzle and React with Redux Saga #

This section deals with Kuzzle (+ Javascript SDK) and React (with Redux and Redux Saga). 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 yarn:

yarn create react-app kuzzle-playground
cd kuzzle-playground
yarn add kuzzle-sdk redux redux-saga react-redux

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 do this, we have to create src/services/kuzzle.js file to put our kuzzle instance, a bit like a singleton:

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, import the kuzzle service file:

import kuzzle from './services/kuzzle';

Add the following imports in the same time:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import './App.css';
import ActionCreators from './state/actions';

Now, add the app class and add in the constructor a message property in the state (we'll use it to store the user input message) and a call to an _initialize() function:

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      message: ''
    };
    this._initialize();
  }

After that, create that function with the connection to Kuzzle:

async _initialize() {
  // handler to be notified in case of a connection error
  kuzzle.on('networkError', error => {
    console.error(error.message);
  });
  await kuzzle.connect();

Then we will establish the connection to kuzzle and create, if they don't exist, the index and collection for our chat.

Add the following lines to the _initialize function:

const exists = await kuzzle.index.exists('chat');
if (!exists) {
  await kuzzle.index.create('chat');
  await kuzzle.collection.create('chat', 'messages');
}

Display the messages #

We'll need some properties and functions to manage our messages.

We have to create our Redux store architecture (more details on Redux documentation), like this:

src
└── state
    ├── actions.js
    ├── reducers.js
    └── sagas.js

Add the following actions to the src/state/actions.js file:

const ActionCreators = {
  sendMessage: text => ({
    type: 'SEND_MESSAGE',
    payload: {
      text
    }
  }),
  setMessages: messages => ({
    type: 'SET_MESSAGES',
    payload: {
      messages
      }
    })
  };
export default ActionCreators;

Then we'll edit the src/state/reducers.js file:

Add the initialState:

const initialState = {
  messages: []
};

And the reducersMap with our SET_MESSAGE action:

const reducersMap = {
  SET_MESSAGES: (state, payload) =>  {
    console.log(state, payload)
   return  {
     messages: [...state.messages, ...payload.messages]
  }
},
  leaveStateUnchanged: state => state
};

Finally, export it:

export default function reducers(state = initialState, action) {
  const reducer = reducersMap[action.type] || reducersMap.leaveStateUnchanged;
  const newState = reducer(state, action.payload, action.meta);
  return newState;
}

The entire file should look like this:

const initialState = {
  messages: []
};
const reducersMap = {
  SET_MESSAGES: (state, payload) =>  {
    console.log(state, payload)
   return  {
     messages: [...state.messages, ...payload.messages]
  }
},
  leaveStateUnchanged: state => state
};
export default function reducers(state = initialState, action) {
  const reducer = reducersMap[action.type] || reducersMap.leaveStateUnchanged;
  const newState = reducer(state, action.payload, action.meta);
  return newState;
}

Now that our store is ready, we'll fetch the existing messages in Kuzzle and add them to our store. Add the following lines to the _initialize() function of the app class in the src/App.js file:

const results = await kuzzle.document.search(
  'chat',
  'messages',
  {} // leave body empty to match all documents
);
if (results.total > 0) {
  this.props.setMessages(results.hits.map(hit => hit._source));
}
this._subscribeToNewMessages();

Then, add the following constants in the render() function of the app class:

render() {
  const { messages } = this.props;
  const { message } = this.state;

And the loop in the return of the render() function to display the messages stored:

<div>
  {[...messages].reverse().map(message => (
    <p key={messages.indexOf(message)}>{message.text}</p>
  ))}
</div>

We can now display the messages stored in Kuzzle. In the next part, we'll see how to create new messages.

Send messages #

We need to write a simple method that will create a new message document in Kuzzle. Add the following function in your app class in the_src/App.js_ file:

sendMessage = event => {
  this.props.sendMessage(this.state.message);
  this.setState({
    message: ''
  });
};

Then, we need to create the sendMessage() Redux action we just called. src/state/sagas.js contains a generator function where we will put our sagas function. (more details on Redux-saga documentation):

Let's add it in the src/state/sagas.js file:

import { takeEvery } from 'redux-saga/effects';
import kuzzle from '../services/kuzzle';
const sendMessage = function*({ payload: { text } }) {
  try {
    const document = {
      text
    };
    yield kuzzle.document.create('chat', 'messages', document);
  } catch (e) {
    console.error(e);
  }
};
export default function*() {
  yield takeEvery('SEND_MESSAGE', sendMessage);
}

As you can see we don't push the new message in our state on message creation. Now, we need to subscribe to changes made on the collection containing our messages. So let's create our _subscribeToNewMessages() function in the app class in src/App.js file. It will call Kuzzle's realtime controller to allow us to receive notifications on message creations:

async _subscribeToNewMessages() {
  kuzzle.realtime.subscribe('chat', 'messages', {}, notif => {
    if (!(notif.type === 'document' && notif.action === 'create')) {
      return;
    }
    const { _source: message } = notif.result;
    this.props.setMessages([message]);
  });
}

Then, just add an input field bound to the message property, and a button calling our sendMessage() function:

<div>
  <input
    type="text"
    name="message"
    id="message"
    value={message}
    onChange={this.handleChange}
  />
  <button onClick={this.sendMessage}>Envoyer</button>
</div>

We need to update our message state property when this input changes. To do that, the onChange event is bound to an handleChange() method. Let's create in the app class:

handleChange = event => {
  this.setState({
    [event.target.id]: event.target.value
  });
};

To finish, just add the export to the src/App.js file:

// connect to redux store
export default connect(
  state => ({
    messages: state.messages
  }),
  {
    sendMessage: ActionCreators.sendMessage,
    setMessages: ActionCreators.setMessages
  }
)(App);

The entire file should look like this:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import './App.css';
import ActionCreators from './state/actions';
import kuzzle from './services/kuzzle';
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      message: ''
    };
    this._initialize();
  }
  async _initialize() {
    // handler to be notified in case of a connection error
    kuzzle.on('networkError', error => {
      console.error(error.message);
    });
    await kuzzle.connect();
    const exists = await kuzzle.index.exists('chat');
    if (!exists) {
      await kuzzle.index.create('chat');
      await kuzzle.collection.create('chat', 'messages');
    }
    const results = await kuzzle.document.search(
      'chat',
      'messages',
      {} // leave body empty to match all documents
    );
    if (results.total > 0) {
      this.props.setMessages(results.hits.map(hit => hit._source));
    }
    this._subscribeToNewMessages();
  }
  async _subscribeToNewMessages() {
    kuzzle.realtime.subscribe('chat', 'messages', {}, notif => {
      if (!(notif.type === 'document' && notif.action === 'create')) {
        return;
      }
      const { _source: message } = notif.result;
      this.props.setMessages([message]);
    });
  }
  handleChange = event => {
    this.setState({
      [event.target.id]: event.target.value
    });
  };
  sendMessage = event => {
    this.props.sendMessage(this.state.message);
    this.setState({
      message: ''
    });
  };
  render() {
    const { messages } = this.props;
    const { message } = this.state;
    return (
      <div>
        <div>
          <input
            type="text"
            name="message"
            id="message"
            value={message}
            onChange={this.handleChange}
          />
          <button onClick={this.sendMessage}>Envoyer</button>
        </div>
        <div>
          {[...messages].reverse().map(message => (
            <p key={messages.indexOf(message)}>{message.text}</p>
          ))}
        </div>
      </div>
    );
  }
}
// connect to redux store
export default connect(
  state => ({
    messages: state.messages
  }),
  {
    sendMessage: ActionCreators.sendMessage,
    setMessages: ActionCreators.setMessages
  }
)(App);

To launch this app, just type the following command:

yarn start

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

Going further #

Now that you're more familiar with Kuzzle with React, you can:

To help you starting a new project with Kuzzle and React, you can start with the Kuzzle, React and Redux boilerplate.