Set up the development environment by installing Expo CLI ( ) https://reactnative.dev/docs/environment-setup yarn global add expo-cli Create a new project (app): expo init todo-app # select "blank (TypeScript)" Start the development server: cd todo-app yarn start # press w to open the web app Create a To-Do app Prerequisites Install a checkbox component: yarn add react-native-bouncy-checkbox Then create the following folders & files structure: ./todo-app/ ... models/ todo.model.ts components/ todo.tsx todo-list.tsx menu.tsx views/ edit-todo.view.tsx ... The Data Model Define the data model in the file : models/todo.model.ts interface ITodo { id: string done: boolean text: string color: string } export default ITodo Basic components Create the component in : Todo components/todo.tsx import React, { useState } from "react"; import { StyleSheet, Text, View } from 'react-native'; import BouncyCheckbox from "react-native-bouncy-checkbox"; import ITodo from "../models/todo.model"; interface ITodoProps { data: ITodo } const Todo = (props: ITodoProps) => { const [isDone, setDone] = useState(false); return ( <View style={[styles.container, {backgroundColor: props.data.color}]}> <BouncyCheckbox fillColor="black" unfillColor="#FFFFFF" iconStyle={{ borderColor: "black" }} isChecked={isDone} onPress={setDone} style={styles.checkbox} /> <Text style={styles.text}>{props.data.text}</Text> </View> ); } export default Todo const styles = StyleSheet.create({ container: { width: '100%', minHeight: '30px', height: 'auto', color: 'black', alignItems: 'center', justifyContent: 'flex-start', display: 'flex', flexDirection: 'row', marginBottom: 5, padding: 10, borderRadius: 5, }, checkbox: { width: 40, minWidth: 40, height: 40, }, text: { color: 'black', width: '100%', } }); Then, the component in : Todo List components/todo-list.tsx import React, { useState } from "react"; import { StyleSheet, FlatList } from 'react-native'; import ITodo from "../models/todo.model"; import Todo from "./todo"; interface ITodoListProps { data: ITodo[] } const TodoList = (props: ITodoListProps) => { const [isDone, setDone] = useState(false); return ( <FlatList style={styles.container} data={props.data} renderItem={ (item: any) => { return ( <Todo data={item.item} /> ) } } keyExtractor={(item, index) => item.id} /> ); } export default TodoList const styles = StyleSheet.create({ container: { // height: '100%', maxHeight: '100%', height: 500, width: '100%', flexDirection: 'column', padding: 10, overflow: 'scroll', }, }); And the in : Main Menu components/menu.tsx import React, { useState } from "react"; import { Button, StyleSheet, Text, View } from 'react-native'; interface IMenu { onAddTodo: () => void } const Menu = (props: IMenu) => { return ( <View style={styles.container}> <Button title="+ Add ToDo" onPress={() => props.onAddTodo()}/> </View> ); } export default Menu const styles = StyleSheet.create({ container: { width: '100%', minHeight: '30px', height: 'auto', color: 'black', alignItems: 'center', justifyContent: 'center', display: 'flex', flexDirection: 'row', padding: 20, }, }); Views Create the in : Add/Edit View view/edit-todo.view.tsx import React, { useState } from "react" import { Modal, StyleSheet, TextInput, View, Text, TouchableOpacity, KeyboardAvoidingView } from 'react-native'; import ITodo from "../models/todo.model"; interface IEditTodoProps { isVisible: boolean onClose: () => void onSave: (data: any) => void data?: ITodo } const EditTodoView = (props: IEditTodoProps) => { const colors = ['#87D3F5', '#BDE991', '#BAAAFB'] const [colorIndex, setColorIndex] = useState(0) const title = props.data ? 'Edit Todo' : 'Add Todo' const [text, setText] = useState(props.data?.text || '') const onSave = () => { if (text.trim().length === 0) { props.onClose() return } if (props.data) { const newData = { ...props.data, text } props.onSave(newData) } else { const newData = { id: 'id-' + Math.floor(Math.random() * 10000000), text, done: false, color: colors[colorIndex], } props.onSave(newData) } } return ( <Modal visible={props.isVisible} style={styles.modal} animationType="slide" transparent={true} > <KeyboardAvoidingView style={styles.container} > <Text style={styles.title}>{title}</Text> <View style={styles.content}> <Text style={styles.label}>ToDo Text:</Text> <TextInput style={styles.input} onChangeText={setText} value={props.data?.text} multiline={true} numberOfLines={10} // keyboardType="numeric" /> <Text style={styles.label}>ToDo Color:</Text> <View style={styles.colors} > <View style={[styles.color, { backgroundColor: colors[0], borderColor: 'black', borderWidth: colorIndex === 0 ? 4 : 0 }]} > <TouchableOpacity style={{height: '100%', width:'100%'}} onPress={() => setColorIndex(0)}> </TouchableOpacity> </View> <View style={[styles.color, { backgroundColor: colors[1], borderColor: 'black', borderWidth: colorIndex === 1 ? 4 : 0}]} > <TouchableOpacity style={{height: '100%', width:'100%'}} onPress={() => setColorIndex(1)}> </TouchableOpacity> </View> <View style={[styles.color, { backgroundColor: colors[2], borderColor: 'black', borderWidth: colorIndex === 2 ? 4 : 0}]} > <TouchableOpacity style={{height: '100%', width:'100%'}} onPress={() => setColorIndex(2)}> </TouchableOpacity> </View> </View> </View> <View style={styles.menu} > <TouchableOpacity style={styles.button} onPress={() => props.onClose()} > <Text style={styles.buttonText}>Cancel</Text> </TouchableOpacity> <TouchableOpacity style={styles.button} onPress={onSave} > <Text style={styles.buttonText}>Save</Text> </TouchableOpacity> </View> </KeyboardAvoidingView> </Modal> ) } export default EditTodoView const styles = StyleSheet.create({ modal: { backgroundColor: 'rgba(0,0,0,0)', }, container: { width: '100%', height: '100%', paddingTop: 100, // backgroundColor: '#fff', backgroundColor: 'rgba(0,0,0,0.7)', flexDirection: 'column', }, content: { backgroundColor: '#fff', flexDirection: 'column', }, title: { fontSize: 24, fontWeight: 'bold', padding: 20, paddingBottom: 0, backgroundColor: '#fff', }, menu: { display: 'flex', width: '100%', height: 60, paddingLeft: 30, paddingTop: 15, flexDirection: 'row', justifyContent: 'space-between', backgroundColor: '#fff' }, input: { height: 'auto', margin: 12, borderWidth: 1, padding: 10, }, label: { padding: 10, paddingBottom: 0, }, colors: { width: '100%', display: 'flex', flexDirection: 'row', padding: 20, }, color: { width: 30, height: 30, marginRight: 20, borderRadius: 3, }, button: { height: 20, width: 100, }, buttonText: { fontSize: 18, color: '#007fff' } }); Main View Update the file with: App.tsx import React, { useState } from 'react'; import { StyleSheet, Text, SafeAreaView } from 'react-native'; import Menu from './components/menu'; import TodoList from './components/todo-list'; import ITodo from './models/todo.model'; import EditTodoView from './views/edit-todo.view'; export default function App() { const [data, setData] = useState<ITodo[]>([]) const [isEditTodoVisible, setIsEditTodoVisible] = useState(false) const onAddTodo = () => { setIsEditTodoVisible(true) } const onCloseEditTodo = () => { setIsEditTodoVisible(false) } const onSaveTodo = (data: ITodo) => { setData((d) => [...d, data]) setIsEditTodoVisible(false) } return ( <SafeAreaView style={styles.container}> <Text style={styles.title}>ToDo</Text> <TodoList data={data} /> <Menu onAddTodo={onAddTodo}/> <EditTodoView isVisible={isEditTodoVisible} onClose={onCloseEditTodo} onSave={onSaveTodo} /> </SafeAreaView> ); } const styles = StyleSheet.create({ container: { display: 'flex', flexDirection: 'column', backgroundColor: '#fff', height: '100%', width: '100%', }, title: { fontSize: 24, fontWeight: 'bold', padding: 20, paddingBottom: 0, }, }); You can find the source code here Also published here