DEV Community

Alex Adam
Alex Adam

Posted on • Originally published at alexadam.dev

4 2

How To Setup React Native and Create a Simple ToDo App

Set up the development environment by installing Expo CLI (https://reactnative.dev/docs/environment-setup)

yarn global add expo-cli
Enter fullscreen mode Exit fullscreen mode

Create a new project (app):

expo init todo-app
# select "blank (TypeScript)"
Enter fullscreen mode Exit fullscreen mode

Start the development server:

cd todo-app
yarn start

# press w to open the web app
Enter fullscreen mode Exit fullscreen mode

Create a To-Do app

Prerequisites

Install a checkbox component:

yarn add react-native-bouncy-checkbox
Enter fullscreen mode Exit fullscreen mode

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
   ...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Basic components

Create the Todo component in 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%',
  }
});
Enter fullscreen mode Exit fullscreen mode

Then, the Todo List component in 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',
  },
});
Enter fullscreen mode Exit fullscreen mode

And the Main Menu in 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,
  },
});
Enter fullscreen mode Exit fullscreen mode

Views

Create the Add/Edit View in 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'
  }
});
Enter fullscreen mode Exit fullscreen mode

Image description

Main View

Update the file App.tsx with:

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,
  },
});
Enter fullscreen mode Exit fullscreen mode

Image description

You can find the source code here

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more