Building a realtime TodoMVC app with ReactiveSearch and appbase.io

Dhruvdutt Jadhav
All things #search
Published in
8 min readMar 7, 2018

--

ReactiveSearch v2.3 is out! 🎉 🎉 🎉

We will build a realtime TodoMVC app using ReactiveSearch v2, an open-source UI components library for Elasticsearch. It provides the building blocks for creating data-driven user interfaces.

TodoMVC is a project which offers the same Todo application implemented using popular JavaScript libraries. It is also known as ‘Speed-dating’ and ‘Rosetta Stone’ for MV* frameworks.

Try out the Live Demo here.

Getting started

Things we will be using for the project:

  • ReactiveSearch: A React components library for building realtime search experiences. We’ll be using it for connecting our UI reactively with the data.
  • appbase.io: Hosted Elasticsearch service with built-in support for streaming realtime changes, which will come handy for our collaborative Todo app.
  • appbase-js: A Javascript / React Native / Node.JS library for using appbase.io. We’ll be using it for firing all the write requests like creating/editing todos.

First of all, let’s setup the backend datastore. Each todo is stored as a JSON that looks like this:

{
id: "73f462af37f1",
title: "Build TodoMVC app",
completed: false,
createdAt: 1506527915473
}
Image: todomvc demo app dataset, use clone button to import this data to your own app

We recommend to clone the app directly with a single click from dejavu’s Clone the App button. It will make things easier and consistent. You can alternatively create your own app to store todos data from https://dashboard.appbase.io.

Next, we’ll use this CodePen for the bare-bone UI setup which includes all necessary libraries and resources.

Image: Check Pen settings to see all the libraries and resources added to the starter pen

As a starting point, we will also use the TodoMVC example implemented in React. Here’s the link to the CodePen with that added. The app structure is as follows:

Image: TodoMVC app breakdown
Image: TodoModel with DBaaS

We will first tie the todoModel with the appbase-js API methods that behaves as DBaaS and connect our app directly with the datastore we created earlier on appbase.io.

The TodoModel class is as follows:

class TodoModel {
constructor (key) {
}

addTodo (title) {
}

toggleAll (checked) {
}

toggle (todoToToggle) {
}

destroy (todo) {
}

save (todoToSave, text) {
}

clearCompleted () {
}
}

These TodoModel methods will be used for handling writes on our data. We will tie them with appbase-js API.

  • constructor initiates the connection by creating appbaseRef, fetches data with search and subscribes to realtime updates with searchStream. Read more about these methods here.
  • addTodo uses index method that writes or replaces a JSON data object at a given type and id location. Read more about the method here.
  • save ,toggle,toggleAll all uses index method. The later method has a forEach wrapper for toggling all todo items.
  • destroy and clearCompleted uses delete method which deletes data objects based on id. The later method again uses a forEach wrapper for deleting all todo items. Read more about the method here.
CodePen: TodoModel connected with appbase-js methods

Breaking down the UI into components

Before we start with integration of Reactive components, let’s get a high level idea of what components we would be using.

Image: Annotated TodoMVC UI. Composing reactive components — ReactiveBase, TextField, ReactiveList

Let’s begin the integration of Reactive components.

#1 Building the base
We’ll use ReactiveBase which is a base wrapper component for all ReactiveSearch components that binds the back-end datastore with the UI components, allowing them to be reactively updated every time there is a change in the datastore or in the UI view components. You can read more about it here.

<ReactiveBase
app="todomvc"
credentials="kIwS5c8TK:bd328e78-ac20-4a02-9fb5-1ec8edffc1d7"
type="todo_reactjs"
>
...
<Component1/>
<Component2/>
...
</ReactiveBase>

This will be the parent component for our app.

  • The necessary props here are app and credentials. Both would be available on the appbase.io dashboard itself. Optionally, we can go to the app’s credentials page for the same.
  • type defines which types should the queries run on.
Image: Copy credentials from dashboard

We will be using the read access token in ReactiveBase for working with reactive UI components and write access token in appbase-js for write methods. Copy it from dashboard.

TodoMVC: After adding our first component ReactiveBase

#2 Adding a new todo

We’ll use TextField component which creates a simple text input field component that is optionally data connected. You can read more about it here.

<TextField
componentId="NewTodoSensor"
onValueChange={this.handleChange}
onKeyDown={this.handleNewTodoKeyDown}
defaultSelected={newTodo}
autoFocus={true}
placeholder="What needs to be done?"
className="new-todo-container"
innerClass={{
input: 'new-todo'
}}
/>

We’ll be using TextField here for adding a new todo.

  • componentId specifies a unique id to identify this component.
  • onValueChange prop is called whenever there is change in the input value. We update the state of newTodo in this method.
  • Input components also support native event methods like onKeyDown which we use here for handling todo submission.
  • defaultSelected is tied with the state via newTodo. We can use this to sync the value, and also clearing the text after adding a todo.
CodePen: Integrating TextField

#3 Building Todos List

We’ll use DataContoller component which creates a UI optional component connected with a custom database query. You can read more about it here.

We won’t directly show this component but rather use it as a pseudo component sensor to show the todos list. We’ll also add a customQuery with match_all, a componentId sensor and set visible to false.

Image: ReactiveList, DataController usage
<DataController
componentId="AllTodosSensor"
visible={false}
showFilter={false}
customQuery={
function(value) {
return {
match_all: {}
}
}
}
/>

Now, to show the list of todos, we’ll connect it with ReactiveList component which is an actuator component to display results in a list layout. You can read more about it here.

<ReactiveList
stream={true}
react={{
or: ["AllTodosSensor"]
}}
scrollOnTarget={window}
showResultStats={false}
pagination={false}
onAllData={this.onAllData}
/>

Some special props used here are:

  • stream that allows to stream updates in the datastore.
  • react defines reactivity based on state changes in any sensor components. You can read more about it here. We’ll use it here to tie it with the DataController we created earlier. This is how the magic happens reactively.
  • scrollOnTarget sets the infinite loading reference; setting it to window will load new results when the window is scrolled.
  • Finally, we’ll set showResultStats and pagination to false to clean the UI by changing their defaults.
  • We also pass an onAllData prop to this component to render out the results in custom elements and components. It accepts a callback function that returns an array with JSX supported components to render view for all list items. The onAllData gets the whole streaming object as its parameter which includes things like newData, currentData and other meta info related to data streams. To make the list consistent and in-place we’ll need to handle this with a custom logic. Those who want to dive deep into this can look at the full code on CodePen. The onAllData function should simply return an array list of final JSX to be shown on UI. It should look something like this:
onAllData(todos, streamData) {// merging all streaming and historic data
const todosData = Utils.mergeTodos(todos, streamData);
return (
<TodoList
todos={todosData}
model={this.props.model}
/>
)
}

We’re also add a custom TodoList container component which simply maps over todos data array and returns a TodoItem component for each item.

The render method of TodoList looks something like this:

todos.map((todo) => {
return (
<TodoItem
key={todo.id}
todo={{...todo}}
onToggle={this.toggle.bind(this, todo)}
onDestroy={this.destroy.bind(this, todo)}
onSave={this.save.bind(this, todo)}
/>
);
})
CodePen: Integrating ReactiveList, DataController

#4 Adding footer controls

We’ll need some custom components for showing footer controls. These aren’t any reactive components. These are components that control how TodoList gets rendered based on All/Active/Completed selection.

Image: TodoFooter, TodoButton usage
<TodoFooter
count={activeTodoCount}
completedCount={completedCount}
nowShowing={this.state.nowShowing}
onClearCompleted={this.clearCompleted}
handleToggle={this.handleToggle}
/>

We’ll compute the activeTodoCount and completedCount variables from todos data array and also connect methods like onClearCompleted with TodoModel directly.

TodoFooter consists of TodoButton component which simply contains a button element and handles toggle. TodoButton’s render method looks something like this:

render() 
return (
<button
className="btn rbc-btn"
onClick={this.handleClick.bind(this)}>
{this.props.label}
</button>
)
}
CodePen: Integrating TodoFooter, TodoButton

#5 Adding active count

We had previously used the pseudo component DataController for showing TodoList. We’ll need it here again to show the active count — “x items left”.

<DataController
componentId="ActiveCountSensor"
visible={false}
showFilter={false}
customQuery={
function(value) {
return {
match_all: {}
}
}
}
/>

We’ll use it ReactiveList again to show “x items left”. First we’ll bind the ActiveCountSensor that we just created with DataController and add it to the react prop. We’ll also enable streaming and define a custom UI with onAllData method

<ReactiveList
stream={true}
onAllData={this.onAllData.bind(this)}
dataField="title"
componentId="ActiveCount"
showResultStats={false}
react={{
or: ["ActiveCountSensor"]
}}
innerClass={{
poweredBy: 'poweredBy'
}}
className="reactivelist"
/>

The onAllData method simply counts the active todos based on the streaming data. It looks like this:

onAllData(todos, streamData) {
const todosData = Utils.mergeTodos(todos, streamData);
let activeTodoCount = todosData.reduce((accum, todo) => {
return todo.completed ? accum : accum + 1;
}, 0);
let activeTodoWord = Utils.pluralize(activeTodoCount, "item"); return (
<span className="todo-count">
<strong>{activeTodoCount}</strong> {activeTodoWord} left
</span>
);
}

That’s it folks. That was our last reactive component. Our realtime TodoMVC app should be up and running.

CodePen: After adding the final actuator component, ReactiveList
TodoMVC: Final app

Here are the links to the final CodePen, GitHub repo and live demo.

You can use this as a starter template to build awesome realtime experiences using ReactiveSearch and appbase.io

Further reading

If you enjoyed reading this post, you should also read our earlier post on how to Build a real-time todo app with React Native.

Have fun building awesome search interfaces with ReactiveSearch and don’t forget to share your latest projects with us. We would ❤ to see what you create, have fun!

--

--