Create a React CRUD TodoList with Modern CSS Styling
Creating a CRUD TodoList application using React, modern CSS, and state management with Redux Toolkit. We will use LocalStorage for persistence and JSON server for the backend. Each task can be edited and marked as completed, with completed tasks visually crossed out using CSS.
Step-by-Step Guide
1. Setting Up the Project
First, set up your React project using Create React App.
npx create-react-app todo-app
cd todo-app
Install necessary dependencies:
npm install @reduxjs/toolkit react-redux axios
2. Setting Up Redux Toolkit
Create a store.js
file to configure the Redux store.
// src/store.js
// Import necessary functions from Redux Toolkit
import { configureStore } from '@reduxjs/toolkit';
// Import the todoReducer we will create in the next step
import todoReducer from './todoSlice';
// Configure the Redux store
const store = configureStore({
reducer: {
todos: todoReducer, // Register the todo reducer under the 'todos' key
},
});
export default store;
3. Creating the Todo Slice
Create a todoSlice.js
file to manage the state of the TodoList.
// src/todoSlice.js
// Import createSlice from Redux Toolkit to easily create a Redux slice
import { createSlice } from '@reduxjs/toolkit';
// Define the initial state of the todos slice
const initialState = [];
// Create a slice for todos
const todoSlice = createSlice({
name: 'todos', // Name of the slice
initialState, // Initial state of the slice
reducers: {
// Reducer to add a new todo
addTodo: (state, action) => {
state.push(action.payload);
},
// Reducer to toggle the completed state of a todo
toggleTodo: (state, action) => {
const todo = state.find(todo => todo.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
// Reducer to edit an existing todo
editTodo: (state, action) => {
const todo = state.find(todo => todo.id === action.payload.id);
if (todo) {
todo.text = action.payload.text;
}
},
// Reducer to delete a todo
deleteTodo: (state, action) => {
return state.filter(todo => todo.id !== action.payload);
},
},
});
// Export the action creators generated by createSlice
export const { addTodo, toggleTodo, editTodo, deleteTodo } = todoSlice.actions;
// Export the reducer to be used in the store
export default todoSlice.reducer;
4. Setting Up LocalStorage
Persist the Redux state to LocalStorage.
// src/store.js
// Import necessary functions from Redux Toolkit
import { configureStore } from '@reduxjs/toolkit';
// Import the todoReducer we created earlier
import todoReducer from './todoSlice';
// Function to save state to LocalStorage
const saveToLocalStorage = state => {
try {
const serializedState = JSON.stringify(state);
localStorage.setItem('todos', serializedState);
} catch (e) {
console.warn(e);
}
};
// Function to load state from LocalStorage
const loadFromLocalStorage = () => {
try {
const serializedState = localStorage.getItem('todos');
return serializedState ? JSON.parse(serializedState) : undefined;
} catch (e) {
console.warn(e);
return undefined;
}
};
// Configure the Redux store with persisted state
const store = configureStore({
reducer: {
todos: todoReducer, // Register the todo reducer under the 'todos' key
},
preloadedState: loadFromLocalStorage(), // Load state from LocalStorage
});
// Subscribe to store updates and save to LocalStorage
store.subscribe(() => saveToLocalStorage(store.getState()));
export default store;
5. Creating the TodoList Component
Create a TodoList.js
component to display the list of todos.
// src/components/TodoList.js
import React, { useEffect } from 'react'; // Import React and useEffect hook
import { useSelector, useDispatch } from 'react-redux'; // Import hooks from react-redux
import { toggleTodo, deleteTodo } from '../todoSlice'; // Import actions from todoSlice
import axios from 'axios'; // Import axios for HTTP requests
import './TodoList.css'; // Import CSS for styling
const TodoList = () => {
const todos = useSelector(state => state.todos); // Get todos from Redux state
const dispatch = useDispatch(); // Get dispatch function
useEffect(() => {
// Fetch todos from JSON server
axios.get('http://localhost:3001/todos')
.then(response => {
response.data.forEach(todo => {
dispatch(addTodo({
id: todo.id,
text: todo.text,
completed: todo.completed,
}));
});
});
}, [dispatch]);
return (
<ul>
{todos.map(todo => (
<li key={todo.id} className={todo.completed ? 'completed' : ''}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => dispatch(toggleTodo(todo.id))}
/>
{todo.text}
<button onClick={() => dispatch(deleteTodo(todo.id))}>Delete</button>
</li>
))}
</ul>
);
};
export default TodoList;
Component Definition:
- The
TodoList
component fetches and displays todos.
- The
State and Dispatch:
useSelector
is used to access the current state of todos.useDispatch
is used to dispatch actions.
Fetching Todos:
useEffect
is used to fetch todos from the JSON server when the component mounts. The todos are added to the Redux store using theaddTodo
action.
Rendering Todos:
- The component maps over the
todos
array to render each todo item. Each item includes a checkbox to toggle its completion state and a button to delete the todo.
- The component maps over the
6. Creating the AddTodo Component
Create an AddTodo.js
component for adding new todos.
// src/components/AddTodo.js
import React, { useState } from 'react'; // Import React and useState hook
import { useDispatch } from 'react-redux'; // Import useDispatch hook from react-redux
import { addTodo } from '../todoSlice'; // Import addTodo action from todoSlice
const AddTodo = () => {
const [text, setText] = useState(''); // Local state for new todo text
const dispatch = useDispatch(); // Get dispatch function
const handleSubmit = e => {
e.preventDefault(); // Prevent default form submission behavior
dispatch(addTodo({
id: Date.now(), // Generate unique ID for new todo
text,
completed: false,
}));
setText(''); // Clear input field after adding todo
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={e => setText(e.target.value)}
/>
<button type="submit">Add Todo</button>
</form>
);
};
export default AddTodo;
Explanation
Importing Libraries:
React
anduseState
are imported from React for component creation and local state management.useDispatch
is imported fromreact-redux
to interact with the Redux store.addTodo
is imported fromtodoSlice
for dispatching the action.
Component Definition:
- The
AddTodo
component is a form for adding new todos.
- The
Local State:
useState
is used to manage the local state for the input text.
Dispatching Action:
- When the form is submitted,
handleSubmit
is called. This function prevents the default form submission behavior, dispatches theaddTodo
action with the new todo, and clears the input field.
- When the form is submitted,
7. Creating the EditTodo Component
Create an EditTodo.js
component for editing existing todos.
// src/components/EditTodo.js
import React, { useState } from 'react'; // Import React and useState hook
import { useDispatch, useSelector } from 'react-redux'; // Import hooks from react-redux
import { editTodo } from '../todoSlice'; // Import editTodo action from todoSlice
const EditTodo = ({ match }) => {
const dispatch = useDispatch(); // Get dispatch function
const todo = useSelector(state =>
state.todos.find(todo => todo.id === parseInt(match.params.id))
); // Find todo to be edited
const [text, setText] = useState(todo ? todo.text : ''); // Local state for updated todo text
const handleSubmit = e => {
e.preventDefault(); // Prevent default form submission behavior
dispatch(editTodo({
id: todo.id,
text,
}));
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={e => setText(e.target.value)}
/>
<button type="submit">Edit Todo</button>
</form>
);
};
export default EditTodo;
Explanation
Importing Libraries:
React
anduseState
are imported from React for component creation and local state management.useDispatch
anduseSelector
are imported fromreact-redux
to interact with the Redux store.editTodo
is imported fromtodoSlice
for dispatching the action.
Component Definition:
- The
EditTodo
component is a form for editing an
- The
existing todo.
Fetching the Todo:
useSelector
is used to find the specific todo from the Redux state that matches theid
provided in the URL parameters (match.params.id
).
Local State:
useState
is used to manage the local state for the input text, initialized with the current text of the todo to be edited.
Dispatching Action:
- When the form is submitted,
handleSubmit
is called. This function prevents the default form submission behavior, dispatches theeditTodo
action with the updated todo, and updates the Redux state.
- When the form is submitted,
8. Adding CSS for Styling
Create a TodoList.css
file for modern CSS styling.
/* src/components/TodoList.css */
/* Style the list */
ul {
list-style-type: none;
padding: 0;
}
/* Style list items */
li {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
border-bottom: 1px solid #ccc;
}
/* Style completed todos */
li.completed {
text-decoration: line-through;
color: #999;
}
/* Style the delete button */
button {
background-color: #f44336;
color: white;
border: none;
padding: 5px 10px;
cursor: pointer;
}
/* Change button color on hover */
button:hover {
background-color: #d32f2f;
}
Explanation
- ul: Removes the default list styling and padding.
- li: Styles each list item to be flexible, aligning items in the center and spacing them evenly. Adds padding and a bottom border.
- li.completed: Adds a line-through effect and changes the text color for completed tasks.
- button: Styles the delete button with a red background, white text, no border, padding, and a pointer cursor.
- button
9. Setting Up JSON Server
Set up a JSON server to handle backend operations.
Install JSON server globally:
npm install -g json-server
Create a db.json
file with some initial data.
// db.json
{
"todos": [
{ "id": 1, "text": "Learn React", "completed": false },
{ "id": 2, "text": "Learn Redux", "completed": false }
]
}
Start the JSON server:
json-server --watch db.json --port 3001
Explanation
- Install JSON Server: JSON server allows you to quickly set up a REST API using a JSON file as the database.
- Create
db.json
: This file contains initial data for the todos. - Start JSON Server: The command
json-server --watch db.json --port 3001
starts the server and watches for changes indb.json
.
10. Integrating JSON Server with Axios
Fetch data from the JSON server in your components using Axios.
// src/components/TodoList.js
import React, { useEffect } from 'react'; // Import React and useEffect hook
import axios from 'axios'; // Import axios for HTTP requests
import { useSelector, useDispatch } from 'react-redux'; // Import hooks from react-redux
import { addTodo, toggleTodo, deleteTodo } from '../todoSlice'; // Import actions from todoSlice
const TodoList = () => {
const todos = useSelector(state => state.todos); // Get todos from Redux state
const dispatch = useDispatch(); // Get dispatch function
useEffect(() => {
// Fetch todos from JSON server
axios.get('http://localhost:3001/todos')
.then(response => {
response.data.forEach(todo => {
dispatch(addTodo({
id: todo.id,
text: todo.text,
completed: todo.completed,
}));
});
});
}, [dispatch]);
return (
<ul>
{todos.map(todo => (
<li key={todo.id} className={todo.completed ? 'completed' : ''}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => dispatch(toggleTodo(todo.id))}
/>
{todo.text}
<button onClick={() => dispatch(deleteTodo(todo.id))}>Delete</button>
</li>
))}
</ul>
);
};
export default TodoList;
Explanation
Importing Libraries and Components:
React
anduseEffect
are imported from React for component creation and side effects.axios
is imported for making HTTP requests.useSelector
anduseDispatch
are imported fromreact-redux
to interact with the Redux store.addTodo
,toggleTodo
, anddeleteTodo
are imported fromtodoSlice
for dispatching actions.
Component Definition:
- The
TodoList
component fetches and displays todos.
- The
State and Dispatch:
useSelector
is used to access the current state of todos.useDispatch
is used to dispatch actions.
Fetching Todos:
useEffect
is used to fetch todos from the JSON server when the component mounts. The todos are added to the Redux store using theaddTodo
action.
Rendering Todos:
- The component maps over the
todos
array to render each todo item. Each item includes a checkbox to toggle its completion state and a button to delete the todo.
- The component maps over the
Conclusion
Congratulations! You’ve built a modern React CRUD TodoList application with Redux Toolkit, LocalStorage, and JSON server. Your TodoList is fully functional and styled with modern CSS, providing a sleek user experience.
Welcome to DevTechTutor.com, your ultimate resource for mastering web development and technology! Whether you're a beginner eager to dive into coding or an experienced developer looking to sharpen your skills, DevTechTutor.com is here to guide you every step of the way. Our mission is to make learning web development accessible, engaging, and effective.