Aplikacija todo dotiče se svih važnih dijelova izrade bilo koje aplikacije vođene podacima, uključujući C reate, R ead, U pdate i D elete (CRUD) operacije. U ovoj ću priči graditi todo aplikaciju s jednim od najpopularnijih mobilnih okvira, React Native .
Koristit ću ReactiveSearch Native , biblioteku otvorenog koda koja pruža React Native UI komponente i pojednostavljuje izgradnju aplikacija na temelju podataka.
Evo što ću graditi u ovoj priči:

Provjerite aplikaciju na snacku ili na izložbi.
Što je React Native?
Evo što kažu dokumenti:
React Native omogućuje vam izradu mobilnih aplikacija koristeći samo JavaScript. Koristi isti dizajn kao React, što vam omogućuje sastavljanje bogatog mobilnog korisničkog sučelja od deklarativnih komponenata.Čak i ako tek započinjete s Reactom ili React Nativeom, trebali biste biti u mogućnosti slijediti ovu priču i izraditi vlastitu vlastitu aplikaciju za todo u stvarnom vremenu.
Zašto koristiti ReactiveSearch? ⚛
ReactiveSearch je biblioteka komponenata React i React Native UI otvorenog koda za Elasticsearch, čiji sam koautor s nekim sjajnim ljudima. Pruža razne React Native komponente koje se mogu povezati s bilo kojim Elasticsearch klasterom.
Napisao sam još jednu priču o Izradi GitHub Repo Explorer-a s React-om i Elasticsearch-om koju možete potražiti za kratki pregled Elasticsearch-a. Čak i ako niste imali iskustva s Elasticsearchom, trebali biste biti u mogućnosti slijediti ovu priču u redu.
Postavljanje stvari ⚒
Ovdje ćemo koristiti React Native verziju knjižnice.
Prije nego što započnemo s izradom korisničkog sučelja, morat ćemo stvoriti pohranu podataka u Elasticsearchu. ReactiveSearch radi s bilo kojim indeksom Elasticsearch i lako ga možete koristiti sa vlastitim skupom podataka.

Radi kratkoće, možete izravno koristiti moj skup podataka ili ga stvoriti za sebe pomoću appbase.io koji vam omogućuje stvaranje hostiranog indeksa Elasticsearch (aka aplikacija).
Svi zadaci strukturirani su u sljedećem formatu:
{ "title": "react-native", "completed": true, "createdAt": 1518449005768 }
Početni projekt
Prije nego što započnemo, preporučio bih instaliranje pređe. Na Linuxu se to može učiniti jednostavnim dodavanjem spremišta pređe i pokretanjem instalacijske naredbe putem upravitelja paketa. Na Macu biste prvo trebali instalirati Homebrew da biste pojednostavili stvari. Ovdje su dokumenti za instalaciju pređe za više detalja. Sljedeća stvar koju možete instalirati je čuvar. Njegova je usluga za praćenje datoteka koja će pomoći reagirajućem izvornom paketu da nesmetano radi.
Ovdje sam početni projekt postavio s create-response-native-app u GitHub grani ovdje. Možete preuzeti zip ili klonirati osnovnu granu pokretanjem sljedeće naredbe:?
git clone -b base //github.com/appbaseio-apps/todos-native
- Sljedeće instalirajte ovisnosti i pokrenite paket:
cd todos-native && yarn && yarn start
- Nakon pokretanja pakirača, aplikaciju možete pokrenuti na telefonu pomoću aplikacije Expo ili pomoću Android ili IOS emulatora:

Uroniti u kod?
Nakon što klonirate kôd iz osnovne grane, trebali biste vidjeti strukturu direktorija kao dolje:
navigation ├── RootComponent.js // Root component for our app ├── MainTabNavigator.js // Tab navigation component screens ├── TodosScreen.js // Renders the TodosContainer components ├── Header.js // Header component ├── AddTodo.js // Add todo input ├── AddTodoButton.js // Add todo floating button ├── TodoItem.js // The todo item ├── TodosContainer.js // Todos main container api ├── todos.js // APIs for performing writes constants // All types of constants used in app types // Todo type to be used with prop-types utils // Streaming logic goes here
Hajde da raščlanimo s čime dolazi osnovno postavljanje:
1. Navigacija
- Sve potrebne konfiguracije za povezivanje s Elasticsearchom su na
constants/Config.js
. - Za prikaz zaslona Svi , Aktivni i Dovršeni koristimo TabNavigator iz reakcijske navigacije . To je donio
navigation/RootComponent.js
. Primijetit ćete daRootComponent
obavija sve unutarReactiveBase
komponente iz ReactiveSearch-a. Ova komponenta pruža sve potrebne podatke za podređene komponente ReactiveSearch. Ovdje možete povezati svoj vlastiti indeks Elasticsearch samo ažuriranjem konfiguracija uconstants/Config.js
.
Logika navigacije prisutna je u navigation/MainNavigator.js
. Pogledajmo kako to funkcionira. Evo dokumenata za navigaciju karticama ako se želite na što referencirati.
import React from 'react'; import { MaterialIcons } from '@expo/vector-icons'; import { TabNavigator, TabBarBottom } from 'react-navigation'; import Colors from '../constants/Colors'; import CONSTANTS from '../constants'; import TodosScreen from '../screens/TodosScreen'; const commonNavigationOptions = ({ navigation }) => ({ header: null, title: navigation.state.routeName, }); // we just pass these to render different routes const routeOptions = { screen: TodosScreen, navigationOptions: commonNavigationOptions, }; // different routes for all, active and completed todos const TabNav = TabNavigator( { [CONSTANTS.ALL]: routeOptions, [CONSTANTS.ACTIVE]: routeOptions, [CONSTANTS.COMPLETED]: routeOptions, }, { navigationOptions: ({ navigation }) => ({ // this tells us which icon to render on the tabs tabBarIcon: ({ focused }) => { const { routeName } = navigation.state; let iconName; switch (routeName) { case CONSTANTS.ALL: iconName = 'format-list-bulleted'; break; case CONSTANTS.ACTIVE: iconName = 'filter-center-focus'; break; case CONSTANTS.COMPLETED: iconName = 'playlist-add-check'; } return ( ); }, }), // for rendering the tabs at bottom tabBarComponent: TabBarBottom, tabBarPosition: 'bottom', animationEnabled: true, swipeEnabled: true, }, ); export default TabNav;
TabNavigator
Funkcije prihvaća dva argumenta, prvi je konfiguracije rute a druga seTabNavigator
konfiguracije. U gornjem isječku prenosimo konfiguracije za prikaz navigacijske trake na dnu i postavljanje različitih ikona za svaku karticu.
2. TodosScreen i TodosContainer
TodosScreen
Komponenta u screens/TodosScreen.js
obavija naš glavni TodosContainer
sastojak u components/TodosContainer.js
kojoj ćemo biti dodajući razne komponente za aplikaciju. TodosContainer
Će pokazati filtrirane podatke, ovisno o tome da smo na sve , Active, ili popunjenog kartici.
3. API-ji za stvaranje, ažuriranje i brisanje zadataka
API-ji za CUD operacije na Elasticsearch-u su prisutni u api/todos.js
. Sadrži tri metode jednostavne add
, update
i destroy
koji rade s bilo Elasticsearch indeksa kao što je navedeno u constants/Config.js
. Važno je imati na umu da će svaka zadaća koju izradimo imati jedinstveno _id
polje. Ovo _id
polje možemo koristiti za ažuriranje ili brisanje postojećeg zadatka.
Za našu aplikaciju trebat će nam samo tri metode za dodavanje, stvaranje ili brisanje zadataka. Međutim, detaljno objašnjenje o API metodama možete pronaći u dokumentima.
Izrada komponenata i korisničkog sučelja?
Počnimo dodavati neke komponente kako bismo upotpunili funkcionalnost aplikacije.
1. Dodavanje Todosa
Upotrijebit ćemo Fab
od native-base
za prikaz plutajućeg gumba za dodavanje zadataka.

const AddTodoButton = ({ onPress }) => ( );
Now you can use this component in components/TodosContainer.js
.
import AddTodoButton from './AddTodoButton'; ... export default class TodosContainer extends React.Component { render() { return ( ... ); } }
Once we’ve added the button, we’ll see something like this:

Now, when someones clicks on this button, we’ll need to show the input for adding a todo. Lets add the code for this in components/AddTodo.js
.
class AddTodo extends Component { constructor(props) { super(props); const { title, completed, createdAt } = this.props.todo; this.state = { title, completed, createdAt, }; } onSubmit = () => { if (this.state.title.length > 0) this.props.onAdd(this.state); return null; }; setStateUtil = (property, value = undefined) => { this.setState({ [property]: value, }); }; render() { const { title, completed } = this.state; const { onBlur } = this.props; return ( this.setStateUtil('completed', !completed)} /> this.setStateUtil('title', changedTitle)} value={title} autoCorrect={false} autoCapitalize="none" onBlur={onBlur} /> this.props.onCancelDelete} style={{ paddingLeft: 25, paddingRight: 15 }} > 0 ? 'black' : 'grey'}`} size={23} /> ); } }
The main components used here are TextInput
, Checkbox
and Ionicons
with straightforward props. We’re using title
and completed
from the state
. We’ll be passing the props todo
, onAdd
, onCancelDelete
and onBlur
from the components/TodosContainer.js
. These will help us in adding new todos or resetting the view if you wish to cancel adding todos.
Now we can update components/TodosContainer.js
with the required changes for rendering AddTodo
component:
... import AddTodoButton from './AddTodoButton'; import AddTodo from './AddTodo'; import TodoModel from '../api/todos'; ... // will render todos based on the active screen: all, active or completed export default class TodosContainer extends React.Component { state = { addingTodo: false, }; componentDidMount() { // includes the methods for creation, updation and deletion this.api = new TodoModel('react-todos'); } render() { return ( {this.state.addingTodo ? ( { this.setState({ addingTodo: false }); this.api.add(todo); }} onCancelDelete={() => this.setState({ addingTodo: false })} onBlur={() => this.setState({ addingTodo: false })} /> ) : null} this.setState({ addingTodo: true })} /> ); } }
The AddTodo
component is rendered inside a ScrollView
component. We also pass an onPress
prop to the AddTodoButton
to toggle the state and conditionally display the AddTodo
component based on this.state.addingTodo
. The onAdd
prop passed to AddTodo
also creates a new todo using the add
API at api/todos.js
.
After clicking the add button, we’ll see the input for adding a todo like this:

2. Displaying Todos
After you finish adding a todo, it’s added into Elasticsearch (which we configured in constants/Config.js
). All this data can be viewed in realtime by using ReactiveSearch Native components.
There are over 10 native UI components that the library provides. For our todo app, we will primarily utilize the ReactiveList component to show the state of todos.
Lets add the ReactiveList
component and get our todos displaying. We’ll add this component in components/TodosContainer.js
and the necessary methods for it to work. Here’s how the ReactiveList
will be used:
... import { ReactiveList } from '@appbaseio/reactivesearch-native'; ... export default class TodosContainer extends React.Component { render() { return ( ({ query: { match_all: {}, }, })} stream onAllData={this.onAllData} dataField="title" showResultStats={false} pagination={false} /> ... this.setState({ addingTodo: true })} /> ); } }
We haven’t added the onAllData
method yet, but let’s understand a bit about the props that we have used here:
componentId
— unique identifier for the component.defaultQuery
: the query to be applied initially for the list. We’ll usematch_all
to show all the todos in default case.stream
: whether to stream new result updates or just show historical results. By setting this totrue
, we now also listen for the live Todo updates. We’ll add the streaming related logic later.onAllData
— a callback function which receives the list of current todo items and the streaming (new todos and any updates) and returns a React component or JSX to render. Here’s how the syntax looks like:
You can read more about all of these props in detail on the ReactiveList’s docs page.
To see something, we’ll need to return a JSX or React component from onAllData
callback. For this, we will use React Native’s FlatList which is composed of Text components. In the next step we’ll add our custom TodoItem
component.
... import { ScrollView, StyleSheet, StatusBar, FlatList, Text } from 'react-native'; import CONSTANTS from '../constants'; ... export default class TodosContainer extends React.Component { ... onAllData = (todos, streamData) => Completed] const filteredData = this.filterTodosData(todos); return ( item._id renderItem={({ item: todo }) => ( {todo.title} )} /> ); }; filterTodosData = (todosData) => { const { screen } = this.props; switch (screen) { case CONSTANTS.ALL: return todosData; case CONSTANTS.ACTIVE: return todosData.filter(todo => !todo.completed); case CONSTANTS.COMPLETED: return todosData.filter(todo => todo.completed); } return todosData; }; render() { ... } }

3. Adding TodoItem(s)
Next, we’ll create a separate component TodoItem for showing each todo which will contain all necessary markups for a todo item like the CheckBox, Text, and a delete Icon. This goes in components/TodoItem.js
:
class TodoItem extends Component { onTodoItemToggle = (todo, propAction) => { propAction({ ...todo, completed: !todo.completed, }); }; render() { const { todo, onUpdate, onDelete } = this.props; return ( this.onTodoItemToggle(todo, onUpdate)} style={{ flex: 1, width: '100%', flexDirection: 'row', }} > this.onTodoItemToggle(todo, onUpdate)} /> {todo.title} onDelete(todo)} style={{ paddingLeft: 25, paddingRight: 15 }} > 0 ? 'black' : 'grey'}`} size={23} /> ); } }
This component gets the todo
from its props along with onDelete
and onUpdate
which are used to update and delete the todo item respectively. We’re using these at the necessary places using the onPress
prop of the components we’re using.
Next, we can import
and use the TodoItem
component in our onAllData
in components/TodosContainer.js
. We’ll pass the todo
as a prop along with the API methods for update
and destroy
which will be used by TodoItem
component.
class TodosContainer extends Component { ... onAllData = (todos, streamData) => { ... return ( ( )} /> ); } }

4. Streaming Data Updates
You might have noticed that the todos are displaying fine, except you’re unable to view updated todos without refreshing the app. In this final step, we’re going to fit that missing part of the puzzle.
In the previous section, we added an onAllData
method for the ReactiveList
component. The second parameter of onAllData
receives streaming updates which we’re going to utilize to always keep the todos updated. Here’s how the updated onAllData
method will look like in components/TodosContainer.js
.
import Utils from '../utils'; ... export default class TodosContainer extends React.Component { ... onAllData = (todos, streamData) => // merge streaming todos data along with current todos const todosData = Utils.mergeTodos(todos, streamData); // filter data based on "screen": [All renderItem={({ item: todo }) => ( )} /> ); }; ... }
The mergeTodos
method is present in utils/index.js
. Here’s how it works:
class Utils { static mergeTodos(todos, streamData) { // generate an array of ids of streamData const streamDataIds = streamData.map(todo => todo._id); return ( todos // consider streamData as the source of truth // first take existing todos which are not present in stream data .filter(({ _id }) => !streamDataIds.includes(_id)) // then add todos from stream data .concat(streamData) // remove todos which are deleted in stream data .filter(todo => !todo._deleted) // finally sort on the basis of creation timestamp .sort((a, b) => a.createdAt - b.createdAt) ); } } export default Utils;
The streamData
receives an array of todo objects when they’re created, deleted, or updated. If an object is updated, it contains a _updated
key set to true
. Similarly, if an object is deleted, it contains a _deleted
key set to true
. If an object is created, it contains neither of the two. Using these points, we’ve added the mergeTodos
function.
With this, you should be able to see the changes to todo items in realtime! If you have an additional device/emulator running the same app, both will stream new updates too. ?
Useful links
- Todos app demo, expo link, starter project and final source code
- ReactiveSearch GitHub repo ⭐️
- ReactiveSearch docs
Hope you enjoyed this story. If you have any thoughts or suggestions, please let me know and have fun!
You may follow me on twitter for latest updates. I've also started posting more recent posts on my personal blog.
Special thanks to Dhruvdutt Jadhav for helping me with this story and the Todos app.