Building a realtime TodoMVC app with ReactiveSearch and appbase.io
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
}
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.
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:
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 withsearch
and subscribes to realtime updates withsearchStream
. Read more about these methods here.addTodo
usesindex
method that writes or replaces a JSON data object at a giventype
andid
location. Read more about the method here.save
,toggle
,toggleAll
all usesindex
method. The later method has a forEach wrapper for toggling all todo items.destroy
andclearCompleted
usesdelete
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.
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.
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
andcredentials
. 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.
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.
#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 ofnewTodo
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 vianewTodo
. We can use this to sync the value, and also clearing the text after adding a todo.
#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.
<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 towindow
will load new results when the window is scrolled.- Finally, we’ll set
showResultStats
andpagination
tofalse
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. TheonAllData
gets the whole streaming object as its parameter which includes things likenewData
,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. TheonAllData
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)}
/>
);
})
#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.
<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>
)
}
#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.
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.
- All ReactiveSearch components can be interactively tried using the playground https://opensource.appbase.io/playground.
- The documentation for all the above plus 20 other components is available at https://opensource.appbase.io/reactive-manual.
- Go ★ the project on GitHub so you can find it when you need to build that awesome search!
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!