In this section you’ll integrate Amplify DataStore with your app, and learn to use the generated data model to create, update, query, and delete Todo items by building an app. You can find the source code of the final Todo App [on our GitHub repository](https://github.com/chintannp/amplified_todo_rn). import polyfillCallout from '/src/fragments/lib/datastore/react-native/getting-started/polyfills.mdx'; ## Boilerplate UI First, you will create some files and place the boilerplate UI code in it. #### App.js First, replace the contents of your **App.js** file with the following UI boilerplate code. Here, you are importing your Home component. NOTE: You have already configured Amplify using `Amplify.configure` in __index.js__ for React Native CLI. So, you don't need to configure Amplify in **App.js**. ```jsx import React from 'react'; import { StatusBar, StyleSheet, View } from 'react-native'; import { Amplify } from 'aws-amplify'; import awsconfig from './src/aws-exports'; import Home from './src/Home'; Amplify.configure(awsconfig); export default function App() { return ( ); } const styles = StyleSheet.create({ container: { backgroundColor: '#fff', flex: 1, }, }); ``` ```jsx import React from 'react'; import { StatusBar, StyleSheet, View } from 'react-native'; import Home from './src/Home'; export default function App() { return ( ); } const styles = StyleSheet.create({ container: { backgroundColor: '#fff', flex: 1, }, }); ``` #### Home.js Next, you will create a Home component for your app which will implement most of the CRUD functionality for your app. Create a new file **Home.js** at location `src/Home.js`. Here, you have total four components defined : 1. **Header**: Simple Header component with Title. 2. **AddModal**: Used for displaying a Modal when a new Todo needs to be added. 3. **TodoList**: Used for displaying the list of Todos. 4. **Home**: Default component that wraps all the above component and a Button for adding a new Todo item. Copy the below boilerplate UI code into the **Home.js** file. ```jsx import React, { useState, useEffect } from 'react'; import { FlatList, Modal, Pressable, StyleSheet, Text, TextInput, View, Platform, } from 'react-native'; import { DataStore } from 'aws-amplify'; import { Todo } from './models'; const Header = () => ( My Todo List ); const AddTodoModal = ({ modalVisible, setModalVisible }) => { const [name, setName] = useState(''); const [description, setDescription] = useState(''); async function addTodo() { //to be filled in a later step } function closeModal() { setModalVisible(false); } return ( X Save Todo ); }; const TodoList = () => { const [todos, setTodos] = useState([]); useEffect(() => { //to be filled in a later step }, []); async function deleteTodo(todo) { //to be filled in a later step } async function setComplete(updateValue, todo) { //to be filled in a later step } const renderItem = ({ item }) => ( { deleteTodo(item); }} onPress={() => { setComplete(!item.isComplete, item); }} style={styles.todoContainer} > {item.name} {`\n${item.description}`} {item.isComplete ? '✓' : ''} ); return ( id} renderItem={renderItem} /> ); }; const Home = () => { const [modalVisible, setModalVisible] = useState(false); return ( <>
{ setModalVisible(true); }} style={[styles.buttonContainer, styles.floatingButton]} > + Add Todo ); }; const styles = StyleSheet.create({ headerContainer: { backgroundColor: '#4696ec', paddingTop: Platform.OS === 'ios' ? 44 : 0, }, headerTitle: { color: '#fff', fontSize: 20, fontWeight: '600', paddingVertical: 16, textAlign: 'center', }, todoContainer: { alignItems: 'center', backgroundColor: '#fff', borderRadius: 2, elevation: 4, flexDirection: 'row', marginHorizontal: 8, marginVertical: 4, padding: 8, shadowOffset: { height: 1, width: 1, }, shadowOpacity: 0.3, shadowRadius: 2, }, todoHeading: { fontSize: 20, fontWeight: '600', }, checkbox: { borderRadius: 2, borderWidth: 2, fontWeight: '700', height: 20, marginLeft: 'auto', textAlign: 'center', width: 20, }, completedCheckbox: { backgroundColor: '#000', color: '#fff', }, buttonText: { color: '#fff', fontWeight: '600', padding: 16, }, buttonContainer: { alignSelf: 'center', backgroundColor: '#4696ec', borderRadius: 99, paddingHorizontal: 8, }, floatingButton: { position: 'absolute', bottom: 44, elevation: 6, shadowOffset: { height: 4, width: 1, }, shadowOpacity: 0.3, shadowRadius: 4, }, modalContainer: { backgroundColor: 'rgba(0,0,0,0.5)', flex: 1, justifyContent: 'center', padding: 16, }, modalInnerContainer: { backgroundColor: '#fff', borderRadius: 16, justifyContent: 'center', padding: 16, }, modalInput: { borderBottomWidth: 1, marginBottom: 16, padding: 8, }, modalDismissButton: { marginLeft: 'auto', }, modalDismissText: { fontSize: 20, fontWeight: '700', }, }); export default Home; ``` Go ahead and run your code now and you should see an app with empty todolist and a floating action button but not much else. import reactnative1 from '/src/fragments/lib/project-setup/react-native/run-react-native.mdx'; Let’s next manipulate the data and add more functionality to your app. ## Manipulating data ### Creating a Todo The *Add Todo* floating action button opens up a Modal to add todos. But, right now, the form does nothing when the *Save* button is pressed. Let’s fix that by having it save a **Todo** to DataStore. Open the **Home.js** file and update the `addTodo()` function in the **AddTodoModal** component. ```jsx async function addTodo() { await DataStore.save(new Todo({ name, description, isComplete: false })); setModalVisible(false); setName(''); setDescription(''); } ``` If you try to add todos using the form now, it should successfully close the form when pressing the *Save* button. But your Todo list is still empty even if you restart the app! After initializing your todos as an empty list, you aren't currently updating it again. You will remedy that in the next step. You will see a console error when you run your app "Datastore - Data won't synchronize", this is expected and will be fixed in a future step once you connect your app to the cloud. ### Querying for Todos and Observing Updates in Real-Time Now that you have a way to add Todo items, you need a way to list them out. You also want to observe updates to those items as they are added, updated, or removed. This can be achieved with `DataStore.observeQuery()`. `observeQuery()` will return a Stream of query snapshots. Each snapshot will contain the current list of items, as well as boolean value to indicate if DataStore's sync process has completed. You can find more info about `observeQuery()` [here](/lib/datastore/real-time/#observe-query-results-in-real-time). You will use the `useEffect()` hook of **TodoList** component to list the todo items. `useEffect()` can be used to perform side effects in function components. Add the below `useEffect()` in the **TodoList** component right after state initialization. You can find more about the useEffect hook [here](https://reactjs.org/docs/hooks-effect.html). ```jsx useEffect(() => { //query the initial todolist and subscribe to data updates const subscription = DataStore.observeQuery(Todo).subscribe((snapshot) => { //isSynced can be used to show a loading spinner when the list is being loaded. const { items, isSynced } = snapshot; setTodos(items); }); //unsubscribe to data updates when component is destroyed so that you don’t introduce a memory leak. return function cleanup() { subscription.unsubscribe(); } }, []); ``` If you restart your app now, you should see that newly added todos will start showing up on the list. The items look like they can be check off and marked as completed, but when pressed, they don’t seem to do anything right now. Let’s learn how to update existing data. ### Updating a Todo Updating an existing data entry looks a lot like creating a new one. It’s important to note, however, that models in DataStore are *immutable*. So, to update a record you must use a model’s `copyOf` function rather than manipulating its properties directly. Update the `setComplete` function in the `TodoList` component. ```jsx async function setComplete(updateValue, todo) { //update the todo item with updateValue await DataStore.save( Todo.copyOf(todo, updated => { updated.isComplete = updateValue }) ); } ``` That’s it! Restart your app and you should now be able to toggle a todo between completed and not completed states. You’re almost done here but what if you want to delete an item from your todo list? We’ll go over how to do that next. ### Deleting a Todo Deleting an existing data entry is even easier than updating one since you don’t need to copy the instance to delete it. It may not be the best user experience to do this but, for this app, you will trigger deletion of a todo item to a long press of the item. Update the `deleteTodo()` function in the `TodoList` component. ```jsx async function deleteTodo(todo) { try { await DataStore.delete(todo); } catch (e) { console.log('Delete failed: $e'); } } ``` Reload your app once more and you should now be able to long press an item to delete it. You can find the complete `Home.js` file [here](https://github.com/chintannp/amplified_todo_rn/blob/main/src/Home.js). Now you have a fully featured CRUD application that saves and retrieves data on the local device, which means this app **works without an AWS account or even an internet connection.** Next, you'll connect it to AWS and make sure the data is available in the cloud.