TodosApp: Sample CRUD React-Native Expo and Firebase
At my office a student intern arrived, and I was assigned to teach the student about mobile applications using React. In training due to lack of preparation, I had to look for sample code material using firebase, various codes found there were various shortcomings to demonstrate CRUD using firebase.
I found a simple TodosApp application that uses the Expo framework and Firebase as a data store. But modified a little so that it can be directly used via Snack in the browser.
Let’s just discuss the source code of the application:
Config.js
Copy the firebase configuration to the config.js file and create a collection in the todos firestore, then you can run this TodosApp application in Snack Expo
import firebase from 'firebase';
const firebaseConfig = {
apiKey: "################",
authDomain: "###################",
projectId: "###############",
storageBucket: "#####################",
messagingSenderId: "################",
appId: "######################",
measurementId: "##############"
};
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
}
export { firebase };
App.js
The main app.js file where the navigation settings for apps use the Stack navigasi navigation type
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import Home from './Screens/Home';
import Detail from './Screens/Detail';
const Stack = createStackNavigator()
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen
name='Home'
component={Home}
/>
<Stack.Screen
name='Detail'
component={Detail}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
Home.js
On the home screen, there is a query to retrieve data at the time of loading screen, namely in the useEffect function, then there is also a function to add and delete data, specifically for editing data, continue on the Details screen.
import React, { useState, useEffect } from 'react';
import {
View,
Text,
FlatList,
StyleSheet,
TextInput,
TouchableOpacity,
Keyboard,
} from 'react-native';
import { firebase } from '../config';
import { FontAwesome } from '@expo/vector-icons';
import { useNavigation } from '@react-navigation/native';
import { SafeAreaView } from 'react-native-safe-area-context';
const Home = () => {
const [todos, setTodos] = useState([]);
const todoRef = firebase.firestore().collection('todos');
const [addData, setAddData] = useState('');
const navigation = useNavigation();
// fetch or read the data from firestore
useEffect(() => {
todoRef.orderBy('createdAt', 'desc').onSnapshot((querySnapshot) => {
const todos = [];
querySnapshot.forEach((doc) => {
const { heading } = doc.data();
todos.push({
id: doc.id,
heading,
});
});
setTodos(todos);
//console.log(users)
});
}, []);
// delete a todo from firestore db
const deleteTodo = (todos) => {
todoRef
.doc(todos.id)
.delete()
.then(() => {
// show a successful alert
alert('Deleted successfully');
})
.catch((error) => {
// show an error alert
alert(error);
});
};
// add a todo
const addTodo = () => {
// check if we have a todo.
if (addData && addData.length > 0) {
// get the timestamp
const timestamp = firebase.firestore.FieldValue.serverTimestamp();
const data = {
heading: addData,
createdAt: timestamp,
};
todoRef
.add(data)
.then(() => {
// release todo state
setAddData('');
// release keyboard
Keyboard.dismiss();
})
.catch((error) => {
// show an alert in case of error
alert(error);
});
}
};
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={styles.textHeadingContainer}>
<Text style={styles.textHeading}>Todos App</Text>
</View>
<View style={styles.formContainer}>
<TextInput
style={styles.input}
placeholder="Add new todo"
placeholderTextColor="#aaaaaa"
onChangeText={(heading) => setAddData(heading)}
value={addData}
underlineColorAndroid="transparent"
autoCapitalize="none"
/>
<TouchableOpacity style={styles.button} onPress={addTodo}>
<Text style={styles.buttonText}>Add</Text>
</TouchableOpacity>
</View>
<FlatList
style={{}}
data={todos}
numColumns={1}
renderItem={({ item }) => (
<View>
<TouchableOpacity
style={styles.container}
onPress={() => navigation.navigate('Detail', { item })}>
<FontAwesome
name="trash-o"
color="red"
onPress={() => deleteTodo(item)}
style={styles.todoIcon}
/>
<View style={styles.innerContainer}>
<Text style={styles.itemHeading}>
{item.heading}
</Text>
</View>
</TouchableOpacity>
</View>
)}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: '#e5e5e5',
padding: 15,
borderRadius: 15,
margin: 5,
marginHorizontal: 10,
flexDirection: 'row',
alignItems: 'center',
},
textHeadingContainer: {
paddingVertical: 20,
alignContent: 'center',
alignItems: 'center',
},
textHeading: {
fontWeight: 'bold',
fontSize: 24,
},
innerContainer: {
alignItems: 'center',
flexDirection: 'column',
marginLeft: 45,
},
itemHeading: {
fontWeight: 'bold',
fontSize: 18,
marginRight: 22,
},
formContainer: {
flexDirection: 'row',
height: 80,
marginLeft: 10,
marginRight: 10,
marginTop: 40,
},
input: {
height: 48,
borderRadius: 5,
overflow: 'hidden',
backgroundColor: 'white',
paddingLeft: 16,
flex: 1,
marginRight: 5,
},
button: {
height: 47,
borderRadius: 5,
backgroundColor: '#788eec',
width: 80,
alignItems: 'center',
justifyContent: 'center',
},
buttonText: {
color: 'white',
fontSize: 20,
},
todoIcon: {
marginTop: 5,
fontSize: 20,
marginLeft: 14,
},
});
export default Home;
Detail.js
Screen Details takes data from the Home parameter submission (route.params.item), which is then displayed in the input field, lastly the user can edit and save after changes occur.
import React, { useState } from 'react';
import {
View,
TextInput,
StyleSheet,
Text,
TouchableOpacity,
} from 'react-native';
import { firebase } from '../config';
import { useNavigation } from '@react-navigation/native';
const Detail = ({ route }) => {
const todoRef = firebase.firestore().collection('todos');
const [textHeading, onChangeHeadingText] = useState(
route.params.item.heading
);
const navigation = useNavigation();
const updateTodo = () => {
if (textHeading && textHeading.length > 0) {
todoRef
.doc(route.params.item.id)
.update({
heading: textHeading,
})
.then(() => {
navigation.navigate('Home');
})
.catch((error) => {
alert(error.message);
});
}
};
return (
<View style={styles.container}>
<TextInput
style={styles.textfield}
onChangeText={onChangeHeadingText}
value={textHeading}
placeholder="Update Todo"
/>
<TouchableOpacity
style={styles.buttonUpdate}
onPress={() => {
updateTodo();
}}>
<Text>UPDATE</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
marginTop: 80,
marginLeft: 15,
marginRight: 15,
},
textfield: {
marginBottom: 10,
padding: 10,
fontSize: 15,
color: '#000000',
backgroundColor: '#e0e0e0',
borderRadius: 5,
},
buttonUpdate: {
marginTop: 25,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 12,
paddingHorizontal: 32,
borderRadius: 4,
elevation: 10,
backgroundColor: '#0de065',
},
});
export default Detail;
Github:
https://github.com/rudiahmad/crud-react-native-expo-and-firebase-todosapp
Changes from source code:
- Change database version from 9 to 8
- Changed the coding so that it can run directly from Expo Snack