By Sanaa Syed (LinkedIn)
Follow the steps here to install Expo.
- macOS and Linux users need to download Watchman
- Open your terminal and create an expo account using the command
npx expo register
(if you already have an expo account usenpx expo login
) - Download the Expo Go App on your phone
You're good to go! π€©
Once you've successfully set up your Expo CLI and have downloaded the Expo Go app, you're ready to create a project! Expo will set you up with all the base code you need to start building your To do list app.
- Create a project named -to-do-list by running the command
npx create-expo-app <your-name>-to-do-list
in your terminal. (Make sure to do this in a location on your computer that you can find easily later on!)You should see something like this when your project has been created:
- Navigate to the project directory using
cd <you-name>-to-do-list
.
- Run
npx expo start
and wait for a QR code to pop up in your terminal. - For Android users, press "Scan QR Code" on the "Home" tab of the Expo Go app and scan the QR code you see in the terminal. For Apple users, open the default Apple "Camera" app and scan the QR code you see in the terminal.
Once you see a screen that shows up like this, you're ready to start building! π₯³
π» You can play around with the code in the
App.js
file if you want to get more familiar with how React Native apps are set up. Add some text and see what changes to get familiar with the language.
- Start by creating a folder called
components
(either by right clicking and adding a new folder in VS Code/any editor or through the terminalmkdir components
). Since React Native is component based, this will make storing all your components/files easier for searchability later on. This is especially useful when you have multiple components but for this project, we'll only be using one. - Within the components folder, create another folder called
Task
and within that folder, create a file calledTask.js
.
You should have a folder structure that looks like this:
βββ components
β βββ Task
β βββ Task.js
Since React Native is built from React, we'll have to import React at the top of any of our .js
files.
At the top of your file, import React
from the library react
and the useState
hook.
import React, { useState } from 'react';
We import useState
with curly brackets because it is a named module.
Next, import the View
, Text
and StyleSheet
components from react-native
.
import { View, Text, StyleSheet } from 'react-native';
The View
component creates a container. You can use it to encapsulate any component and to create a 'section' of code on the page.
In React/React Native, the simplest way to define a component is to write a JavaScript function.
The main Task component will be written in a Javascript anonymous function which will return a React Native element.
This function will accept a single "props" (short for properties) object argument that will hold data and can be used in this file. This makes the component customized when it is created using different parameters called props. This lets you make a single component that is used in many different places in your app, with slightly different properties in each place. You can use these props by calling props.<your-prop-name>
.
Copy and past this template into your Task.js
file:
import React, { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
const Task = (props) => {
return (
<View>
</View>
)
}
// We need to export the Task component so that we can later use it in the App.js file!
export default Task;
A call to the Task
component would look like
<Task text='Attend workshop at CUSEC!' />
where the props we pass in is the text. We can then access this prop in our Task function by calling props.text
.
Using the props defined at the beginning of the Task function, we can add text to our task now. We'll be passing in a text
attribute as the props from the App.js
file later in this workshop. To call it in this file, it would look like: props.text
.
Use the View
and Text
components imported from react-native
to create a Text View within the already defined View
component. This will be helpful for styling later on as it creates a container to hold both the checkbox and the text!
return (
<View>
<View>
<Text>{props.text}</Text>
</View>
</View>
)
When you complete a task, you'll want a way to check it off! We'll be using the Checkbox
component from the expo-checkbox
library.
- Install
expo-checkbox
by runningnpx expo install expo-checkbox
in your terminal. - Add this import to the top of your file:
import Checkbox from 'expo-checkbox';
The useState
hook lets you add React state to function components.
const Example = (props) => {
const [count, setCount] = useState(0)
return <div />;
}
This code basically says that the variable count
has a default value of 0 (count = 0
). The setCount
is a function that will automatically set the variable count
to any other integer value that we give it at any other point in the code.
Note: this default value can be any type (string, integer, boolean...).
const Example = (props) => {
const [count, setCount] = useState(0)
return (
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
);
}
This would increment the count
variable by 1 every time the button is clicked.
Similarly, We'll use the useState
hook to check if the checkbox is selected or not.
-
Add
const [isSelected, setSelection] = useState(false);
right before your return statement in the
Task
function. -
Next, using the
Checkbox
component, we can add<Checkbox value={isSelected} onValueChange={setSelection} color='#3a5a40' />
right before the
Text
component.
The Checkbox
component handles the use of hook for us so we don't have to worry about anything else!
Now your Task
function should look something like this:
const Task = (props) => {
const [isSelected, setSelection] = useState(false);
return (
<View>
<View>
<Checkbox
value={isSelected}
onValueChange={setSelection}
color='#3a5a40'
/>
// OPTIONAL: you can add a strikethrough in the text when the checkbox is selected
<Text style={{textDecorationLine: isSelected ? "line-through" : "none" }}>{props.text}</Text>
</View>
</View>
)
}
Now it's time to add some styles to your component using the StyleSheet
we imported and your CSS skills!
Add a variable called styles above your export like this:
const styles = StyleSheet.create({});
An example of the styling I used:
const styles = StyleSheet.create({
task: {
backgroundColor: '#FFF',
padding: 15,
borderRadius: 10,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 20,
},
taskTextContainer: {
maxWidth: '80%',
},
taskMargin: {
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
},
checkbox: {
width: 24,
height: 24,
opacity: 0.4,
borderRadius: 5,
marginRight: 15,
},
});
You can call these styles in your Task
function by using the style
attribute:
<View style={styles.task} />
Play around with the stylings and add them to your code.
Answer
import React, { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import Checkbox from 'expo-checkbox';
const Task = (props) => {
const [isSelected, setSelection] = useState(false);
return (
<View style={styles.task}>
<View style={styles.taskMargin}>
<Checkbox
value={isSelected}
onValueChange={setSelection}
color='#3a5a40'
style={styles.checkbox}
/>
<Text style={{ ...styles.taskText, textDecorationLine: isSelected ? "line-through" : "none" }}>{props.text}</Text>
</View>
</View>
)
}
const styles = StyleSheet.create({
task: {
backgroundColor: '#FFF',
padding: 15,
borderRadius: 10,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 20,
},
taskTextContainer: {
maxWidth: '80%',
},
taskMargin: {
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
},
checkbox: {
width: 24,
height: 24,
opacity: 0.4,
borderRadius: 5,
marginRight: 15,
},
});
export default Task;
We'll be using the App.js
file as our main screen file. On the main screen, we have three components to add:
- The title
- The input bar
- The container that will hold all the tasks as they are inputted
Similar to Task.js
, we'll want to import the useState
hook from React
and a few components from react-native
.
import React, { useState } from 'react';
import { Text, View, TextInput, TouchableOpacity, Keyboard, KeyboardAvoidingView, ScrollView, StyleSheet } from 'react-native';
Finally, we'll also want to import the Task
component we created.
import Task from './components/Task/Task'
Inside the View
component, replace the default text with the title you want to put for your to do list:
<Text>Today's to do list π</Text>
At this point, you can also remove the default
StatusBar
in theView
.
We'll be using the useState
hook again here with the TextInput
component to store the value that you enter for a task. We'll keep the default state as null
.
Add
const [task, setTask] = useState();
above the return statement.
Note: anytime you create a function or hooks, they must go above your return statement!
The input bar is made up of the TextInput
and a button which will add the task to list of all the inputted tasks.
Add a TextInput
with the following props: placeholder
, value
and onChangeText
.
The onChangeText
is a callback function that is called when the text input's text changes. We use that text to update the state of the variable task
.
It should look something like this:
<TextInput placeholder={'Write a task'} value={task} onChangeText={text => setTask(text)} />
As we've done with the Task
component, we must wrap this text input in a view! However, this time we'll use a different view component called KeyboardAvoidingView
. This is so that the text input component's position and height on the screen will be automatically based on the keyboard height to remain visible while the virtual keyboard is displayed.
Wrap the TextInput
with this:
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
>
<TextInput placeholder={'Write a task'} value={task} onChangeText={text => setTask(text)} />
</KeyboardAvoidingView>
Next, we'll use the TouchableOpacity
component which has an onPress
prop. This component is a button but has the additional functionality of dimming the opacity of the button when it is clicked. When the button is pressed/clicked, we can call a function that we will make called addTask
. The goal of this function is to add the input to a list of tasks.
<TouchableOpacity onPress={() => addTask()}>
<View>
<Text>+</Text>
</View>
</TouchableOpacity>
For the addTask
function, we'll create another useState
hook to store a list and add the default state as an empty array.
const [taskList, setTaskList] = useState([]);
The addTask
function will:
- Dismiss the keyboard that was opens upon tapping the input bar. (
Keyboard.dismiss()
) - Use the
setTaskList
function to add thetask
to thetaskList
. - Use the
setTask
function to reset thetask
variable tonull
.
To add to an array, you can 'spread' the current values and append the new task to the end like so [...currList, newItem]
.
The addTask
function should look like this:
const addTask = () => {
Keyboard.dismiss();
setTaskList([...taskList, task])
setTask(null);
}
Input Bar code
export default function App() {
const [task, setTask] = useState();
const [taskList, setTaskList] = useState([]);
const addTask = () => {
Keyboard.dismiss();
setTaskList([...taskList, task])
setTask(null);
}
return (
<View style={styles.container}>
<Text>Today's to do list π</Text>
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
>
<TextInput placeholder={'Write a task'} value={task} onChangeText={text => setTask(text)} />
<TouchableOpacity onPress={() => addTask()}>
<View>
<Text>+</Text>
</View>
</TouchableOpacity>
</KeyboardAvoidingView>
</View>
);
}
The last piece of code is to add all the tasks from the task list on the screen.
We'll use the ScrollView
component to add a scroll in case the to do list gets too big and the Task
component we created above.
Using the taskList
and the javascript map function, we can iterate through each item on the list and create a Task
component for it like this:
<View>
<ScrollView>
{
taskList.map((item) => {
return (
<Task text={item} />
)
})
}
</ScrollView>
</View>
Now, you can add your styles!
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#a3b18a',
},
tasksWrapper: {
height: '80%'
},
sectionTitle: {
marginTop: 70,
paddingHorizontal: 20,
fontSize: 24,
fontWeight: 'bold',
color: 'white'
},
items: {
marginTop: 10,
marginBottom: 10,
maxHeight: '87%',
paddingHorizontal: 20,
},
inputTask: {
position: 'absolute',
bottom: 60,
width: '100%',
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center'
},
input: {
paddingVertical: 15,
paddingHorizontal: 15,
backgroundColor: '#FFF',
borderRadius: 60,
borderColor: '#588157',
borderWidth: 2,
width: 250,
},
addButton: {
width: 60,
height: 60,
backgroundColor: '#FFF',
borderRadius: 60,
justifyContent: 'center',
alignItems: 'center',
borderColor: '#588157',
borderWidth: 2,
}
});
Answer
import React, { useState } from 'react';
import { Text, View, TextInput, TouchableOpacity, Keyboard, KeyboardAvoidingView, ScrollView, StyleSheet } from 'react-native';
import Task from './components/Task/Task'
export default function App() {
const [task, setTask] = useState();
const [taskList, setTaskList] = useState([]);
const addTask = () => {
Keyboard.dismiss();
setTaskList([...taskList, task])
setTask(null);
}
return (
<View style={styles.container}>
<Text style={styles.sectionTitle}>Today's to do list π</Text>
<View style={styles.tasksWrapper}>
<ScrollView
style={styles.items}
>
{
taskList.map((item, index) => {
return (
<Task text={item} key={index} />
)
})
}
</ScrollView>
</View>
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={styles.inputTask}
>
<TextInput style={styles.input} placeholder={'Write a task'} value={task} onChangeText={text => setTask(text)} />
<TouchableOpacity onPress={() => addTask()}>
<View style={styles.addButton}>
<Text>+</Text>
</View>
</TouchableOpacity>
</KeyboardAvoidingView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#a3b18a',
},
tasksWrapper: {
height: '80%'
},
sectionTitle: {
marginTop: 70,
paddingHorizontal: 20,
fontSize: 24,
fontWeight: 'bold',
color: 'white'
},
items: {
marginTop: 10,
marginBottom: 10,
maxHeight: '87%',
paddingHorizontal: 20,
},
inputTask: {
position: 'absolute',
bottom: 60,
width: '100%',
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center'
},
input: {
paddingVertical: 15,
paddingHorizontal: 15,
backgroundColor: '#FFF',
borderRadius: 60,
borderColor: '#588157',
borderWidth: 2,
width: 250,
},
addButton: {
width: 60,
height: 60,
backgroundColor: '#FFF',
borderRadius: 60,
justifyContent: 'center',
alignItems: 'center',
borderColor: '#588157',
borderWidth: 2,
}
});
All the code can be found in this repository.
Feel free to reach out to me on LinkedIn!!