So erstellen Sie mit React Native eine Echtzeit-Aufgaben-App

A todo app Berührungen auf alle wichtigen Teile eines datengesteuerten app Aufbau, einschließlich der C Reate, R ead, U pdate und D elete (CRUD) Operationen. In dieser Geschichte werde ich eine ToDo-App mit einem der beliebtesten mobilen Frameworks, React Native , erstellen .

Ich werde ReactiveSearch Native verwenden , eine Open-Source-Bibliothek, die React Native-UI-Komponenten bereitstellt und das Erstellen datengesteuerter Apps vereinfacht.

Folgendes werde ich in dieser Geschichte aufbauen:

Schauen Sie sich die App auf Snack oder auf der Messe an.

Was ist React Native?

Folgendes sagen die Dokumente:

Mit React Native können Sie mobile Apps nur mit JavaScript erstellen. Es verwendet das gleiche Design wie React, sodass Sie aus deklarativen Komponenten eine umfangreiche mobile Benutzeroberfläche erstellen können.

Selbst wenn Sie gerade erst mit React oder React Native beginnen, sollten Sie in der Lage sein, diese Geschichte zu verfolgen und Ihre eigene Echtzeit-ToDo-App zu erstellen.

Warum ReactiveSearch verwenden? ⚛

ReactiveSearch ist eine Open-Source-Komponentenbibliothek für React and React Native UI für Elasticsearch, die ich gemeinsam mit einigen großartigen Leuten verfasst habe. Es bietet eine Vielzahl von React Native-Komponenten, die eine Verbindung zu jedem Elasticsearch-Cluster herstellen können.

Ich habe eine weitere Geschichte zum Erstellen eines GitHub Repo-Explorers mit React und Elasticsearch geschrieben, die Sie für einen kurzen Überblick über Elasticsearch lesen können. Auch wenn Sie noch keine Erfahrung mit Elasticsearch haben, sollten Sie in der Lage sein, diese Geschichte gut zu verfolgen.

Dinge einrichten ⚒

Wir werden hier die React Native-Version der Bibliothek verwenden.

Bevor wir mit dem Erstellen der Benutzeroberfläche beginnen, müssen wir in Elasticsearch einen Datenspeicher erstellen. ReactiveSearch funktioniert mit jedem Elasticsearch-Index und kann problemlos mit Ihrem eigenen Datensatz verwendet werden.

Der Kürze halber können Sie meinen Datensatz direkt verwenden oder mithilfe von appbase.io einen eigenen Datensatz erstellen, mit dem Sie einen gehosteten Elasticsearch-Index (auch bekannt als App) erstellen können.

Alle Aufgaben sind im folgenden Format strukturiert:

{ "title": "react-native", "completed": true, "createdAt": 1518449005768 }

Das Starterprojekt

Bevor wir anfangen, würde ich empfehlen, Garn zu installieren. Unter Linux können Sie einfach das Garn-Repository hinzufügen und den Installationsbefehl über Ihren Paketmanager ausführen. Auf einem Mac sollten Sie zuerst Homebrew installieren, um die Arbeit zu vereinfachen. Hier finden Sie die Garninstallationsdokumente für weitere Einzelheiten. Das nächste, was Sie installieren können, ist Watchman. Es ist ein Dateiüberwachungsdienst, der dem reaktionsnativen Packager hilft, reibungslos zu laufen.

Ich habe das Starterprojekt mit create-react-native-app in einem GitHub-Zweig hier eingerichtet. Sie können eine Zip-Datei herunterladen oder den Basiszweig klonen, indem Sie den folgenden Befehl ausführen :?

git clone -b base //github.com/appbaseio-apps/todos-native
  • Installieren Sie als Nächstes die Abhängigkeiten und starten Sie den Packager:
cd todos-native && yarn && yarn start
  • Nach dem Start des Packagers können Sie die App mit der Expo-App oder einem Android- oder IOS-Emulator auf Ihrem Telefon ausführen:

In Code eintauchen?

Nachdem Sie den Code aus dem Basiszweig geklont haben, sollte eine Verzeichnisstruktur wie folgt angezeigt werden:

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

Lassen Sie uns zusammenfassen, was das Basis-Setup beinhaltet:

1. Navigation

  • Alle erforderlichen Konfigurationen für die Verbindung mit Elasticsearch finden Sie unter constants/Config.js.
  • Wir verwenden TabNavigator aus der Reaktionsnavigation, um den Bildschirm Alle , Aktive und Abgeschlossene Aufgaben anzuzeigen. Dies wird von der gerendert navigation/RootComponent.js. Sie werden feststellen, dass RootComponentalles in der ReactiveBaseKomponente von ReactiveSearch umbrochen wird. Diese Komponente stellt den untergeordneten ReactiveSearch-Komponenten alle erforderlichen Daten zur Verfügung. Sie können hier Ihren eigenen Elasticsearch-Index verbinden, indem Sie einfach die Konfigurationen in aktualisieren constants/Config.js.

Die Navigationslogik ist in vorhanden navigation/MainNavigator.js. Lassen Sie uns überlegen, wie es funktioniert. Hier sind die Dokumente für die Tab-Navigation, wenn Sie auf etwas verweisen möchten.

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;
  • Die TabNavigatorFunktion akzeptiert zwei Argumente, das erste sind die Routenkonfigurationen und das zweite sind die TabNavigatorKonfigurationen. Im obigen Snippet übergeben wir die Konfigurationen zum Anzeigen einer Registerkartennavigationsleiste am unteren Rand und zum Festlegen unterschiedlicher Symbole für jede Registerkarte.

2. TodosScreen und TodosContainer

Die TodosScreenKomponente in screens/TodosScreen.jsschließt unsere TodosContainerHauptkomponente ein, in der components/TodosContainer.jswir verschiedene Komponenten für die App hinzufügen. In TodosContainerwerden gefilterte Daten angezeigt, je nachdem, ob wir uns auf der Registerkarte Alle , Aktiv oder Abgeschlossen befinden .

3. APIs for Creating, Updating and Deleting todos

The APIs for CUD operations on Elasticsearch are present in api/todos.js . It contains three simple methods add, update and destroy which work with any Elasticsearch index as specified in constants/Config.js. An important point to keep in mind is that each todo item we create will have a unique _id field. We can use this _id field for updating or deleting an existing todo.

For our app, we’ll just need three methods for adding, creating or deleting todos. However, you can find a detailed explanation about the API methods at the docs.

Building the components and UI ?

Lets start adding some components to complete the functionality of the app.

1. Adding Todos

We’ll use Fab from native-base to render a floating button for adding todos.

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 use match_all to show all the todos in default case.
  • stream: whether to stream new result updates or just show historical results. By setting this to true, 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

  1. Todos app demo, expo link, starter project and final source code
  2. ReactiveSearch GitHub repo ⭐️
  3. 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.