Create Todo List with React Hooks, Context API, React UI and Firebase
👆Hello Everyone, In this tutorial We will create a simple todo list application With React and hooks via Context API Material UI and finally integration with Firebase 🔥
Part 1: Installations and Configurations
- Node.js & Npm (https://nodejs.org/en/download/)
- VSCode (https://code.visualstudio.com/download)
Open terminal / cmd and type:
$ sudo npm install -g create-react-app
$ create-react-app todo-list
$ cd todo-list
Add Material UI
$ npm i --save @material-ui/core$ npm install @material-ui/icons
Go to public folder ,open index.html file and type
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
Add UUID Generator
$ npm i --save uuid
Part 2: Context API
In this part of the article we will show how to use and Implement context API and Reducers.
So let’s get started with creating 2 folders:
- contexts
- reducers
Let’s create TaskContext.js file in context folder.
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
- Create arrow component function with props:
export const TaskContext = createContext();const TaskContextProvider = (props) => {
...
}export default TaskContextProvider;
Then we need to return our provider with dispatcher and tasks items, finally we need to implement “sorted array” which return sorted tasks by isCheked or Not.
return (
<TaskContext.Provider value={{ tasks,sortedTasks, dispatch }}> {props.children}
</TaskContext.Provider>)
The Result:
2. In reducers folder let’s create a new reducer file called TaskReducer.js
So we need a 3 actions method for working with our tasks array, The first Action needed for adding a new task, The second is for checked The task and the third will remove the task.
Let’s create an enum for this static actions
export const Action = {
ADD_TASK: "add-task",
CHECK_TASK: "check-task",
REMOVE_TASK: "remove-task"
}
Let’s created the reducer with a switch case to replace a type of action:
When add task selected the right way is to pushed a new task
case Action.ADD_TASK: {
return [...state, action.task]
}
When the checked type selected, we need to change specific task within the array by task id value
case Action.CHECK_TASK:{
let taskIndex = state.findIndex(t => t.id === action.task.id);
state[taskIndex].isChecked = action.task.isChecked
return state.filter(task => task.id !== action.id);
}
And remove task will be
case Action.REMOVE_TASK: {
return state.filter(task => task.id !== action.id)
}
The Result:
Part 3: Component, hooks and Material UI
In this part of the article we will learn how to create an arrow function component for each one of task event.
Let’s and create components folder in src folder and create 4 components:
- NavbarComponent.js — src/components/NavbarComponent.js
- AddTaskComponent.js — src/components/TodoListComponent.js
- TasksListComponent.js — src/components/tasks/AddTaskComponent.js
- TodoListComponent.js — src/component/tasks/TaskListComponent.js
Note: I decide to create one more folder in components called tasks.
- Open NavbarComponent.js, let’s create new component and useContext
const NavbarComponent = () => { const {tasks} = useContext(TaskContext)}export default NavbarComponent;
Now let’s and make navbar using the material UI
return (
<AppBar position="static">
<Toolbar variant="dense"
style={{ justifyContent: "center" }} >
<Typography
variant="h6"
color="inherit">
React Todo List ({tasks.length})
</Typography>
</Toolbar>
</AppBar>
);
The Result:
https://gist.github.com/shai-benshimol/6a0872107dc9467a3859a9832b60a95c
2. Let’s create a simple component for adding a new task.
3. Let’s create a new component functoin — TasksListComponent and implement useStyles for the background list.
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
backgroundColor: theme.palette.background.transparent,
},
marked: {
textDecoration: 'line-through'
}
}));
Then We need to use Task Context to bringing back data from reducer
const {
sortedTasks,
dispatch,
} = useContext(TaskContext)
Then, return a jsx using material UI
return (
<List className={classes.root}>
{sortedTasks.map((task) => {
return (
<ListItem key={task.id}
role={undefined}
dense
button
onClick={() => {
onChecked(task.id, !task.isChecked)
}
}>
<IconButton color="primary">
{
!task.isChecked ? (<CropFreeIcon />) : (<LibraryAddCheckIcon />)
}
</IconButton>
<ListItemText primary={task.description}
className={task.isChecked ? classes.marked : ''} />
<ListItemSecondaryAction>
<IconButton
edge="end"
aria-label="comments"
onClick={() => {
dispatch({
type: Action.REMOVE_TASK,
id: task.id
})
}}>
<DeleteOutlineIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
The Result:
4. Open TodoListComponent.js that we have created before and create a new component for this state.
In TodoListComponent we want to combine the 2 components, AddTask and TasksList in a column.
Let’s put all together in App.js… So open App.js file and replace to
And that’s it
$ npm run start
Part 4: Firebase
I have already created tutorial about firebase integration
Now, Let’s created a new folder for firebase with 2 sub folders, “firebase.config.js” and “Firebase.js”
In firebase.config.js just add your values from firebase console just like:
import firebase from 'firebase';const firebaseConfig = {
apiKey: "<--->",
authDomain: "<--->",
databaseURL: "<--->",
projectId: "<--->",
storageBucket: "<--->",
messagingSenderId: ""<--->",
appId: "<--->",
measurementId: "<--->"
};firebase.initializeApp(firebaseConfig)export const db = firebase.firestore()
Open “Firebase.js” and add 4 functions
- addTaskReques
- getTasksRequest
- checkTaskRequest
- removeTaskRequest
Add Task Request:
export const addTaskRequest = async (task) => {
return await db.collection(collection)
.add(task)
}
Get Tasks Request
export const getTasksRequest = async () => {
return await db.collection(collection).get().then(res => {
let tasks = [];res.docs.map(task => {
let data = task.data();tasks.push({
id: data.id,
isChecked: data.isChecked,
description: data.description,
created: data.created
})
})
return tasks;
})
}
Check Task Request
export const checkTaskRequest = (id, isChecked) => {
return db.collection(collection)
.doc(id)
.set({
isChecked: isChecked
})
}
Remove Task Request
export const removeTaskRequest = (id) => {
return await db.collection(collection)
.db.collection(collection)
.doc(id)
.delete()
}
Final Result
Part 5: useEffect
The Effect Hook, useEffect, adds the ability to perform side effects from a function component. It serves the same purpose as , componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API. (We’ll show examples comparing useEffect to these methods in Using the Effect Hook.)
Now, let’s make some changes in “TaskComponent.js”, create a useState array for setTasks from the storage of firebase, then create useEffect hook like so:
const [sortedTasks,setTasks] = useState([]);const count = sortedTasks.length;
useEffect(()=>{
getTasksRequest().then(res=>{
setTasks(res.sort((t, f) => (f.isChecked === t.isChecked)? 0 : f.isChecked? -1 : 1))
dispatch({
type:Action.GET_ALL_TASKS,
res
})
})
},[])
Final Result
Conclusion
We learned how to handle local state with Context API and Hooks, building The UI with Material UI and Store a data on firebase cloud.