Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

State and events

State in React Native works the same as on the web: useState keeps the data, event handlers change it, and the screen re-renders. Let us combine the previous lessons into a working to-do list.

The task type and state

Each task is an object with an id, a title and a done flag. Rewrite app/index.tsx:

// app/index.tsx
import { useState } from 'react';
import { Button, StyleSheet, Text, TextInput, View } from 'react-native';
 
type Task = { id: string; title: string; done: boolean };
 
export default function Index() {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [title, setTitle] = useState('');
 
  function addTask() {
    if (title.trim() === '') return;
    setTasks([...tasks, { id: Date.now().toString(), title: title.trim(), done: false }]);
    setTitle('');
  }
 
  return (
    <View style={styles.screen}>
      <TextInput
        style={styles.input}
        value={title}
        onChangeText={setTitle}
        placeholder="New task"
        onSubmitEditing={addTask}
      />
      <Button title="Add" onPress={addTask} />
      {tasks.map((task) => (
        <Text key={task.id} style={styles.task}>
          {task.title}
        </Text>
      ))}
    </View>
  );
}
 
const styles = StyleSheet.create({
  screen: { flex: 1, padding: 16, gap: 8 },
  input: { borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 12 },
  task: { fontSize: 18, paddingVertical: 6 },
});

Type "Buy bread" and press "Add" or the return key on the keyboard: the line appears under the button and the field is cleared. An empty title is not added.

Marking a task done

Wrap the line in Pressable: a short press toggles done. Add the function inside the component and replace the list output:

// app/index.tsx
function toggleTask(id: string) {
  setTasks(tasks.map((t) => (t.id === id ? { ...t, done: !t.done } : t)));
}
 
{tasks.map((task) => (
  <Pressable key={task.id} onPress={() => toggleTask(task.id)}>
    <Text style={[styles.task, task.done && styles.done]}>{task.title}</Text>
  </Pressable>
))}

Add Pressable to the import from react-native and the style done: { color: '#999', textDecorationLine: 'line-through' }. Pressing a task strikes it through, pressing again removes the mark.

Deleting with confirmation

On a phone it is customary to ask before deleting. Alert.alert takes a title, a message and an array of buttons:

// app/index.tsx
function confirmDelete(task: Task) {
  Alert.alert('Delete task?', task.title, [
    { text: 'Cancel', style: 'cancel' },
    {
      text: 'Delete',
      style: 'destructive',
      onPress: () => setTasks(tasks.filter((t) => t.id !== task.id)),
    },
  ]);
}

Pass it to Pressable: onLongPress={() => confirmDelete(task)}. Hold your finger on a task and a dialog with "Cancel" and "Delete" buttons appears.

Common mistake

Changing the state array directly:

// app/index.tsx
// Wrong:
tasks.push({ id: '1', title, done: false });
setTasks(tasks);

React gets the same array and does not re-render, so the task does not appear. Always create a new array with [...tasks, newTask], map or filter, as covered in the React course.

Thanks for reading!

Donate to the site — we work hard for you.