On gists
React TodoList with Redurec
React
App.jsx
Raw
#
import { createContext, useEffect, useReducer, useState } from "react"
import { NewTodoForm } from "./NewTodoForm"
import "./styles.css"
import { TodoFilterForm } from "./TodoFilterForm"
import { TodoList } from "./TodoList"
const LOCAL_STORAGE_KEY = "TODOS"
const ACTIONS = {
ADD: "ADD",
UPDATE: "UPDATE",
TOGGLE: "TOGGLE",
DELETE: "DELETE",
}
function reducer(todos, { type, payload }) {
switch (type) {
case ACTIONS.ADD:
return [
...todos,
{ name: payload.name, completed: false, id: crypto.randomUUID() },
]
case ACTIONS.TOGGLE:
return todos.map(todo => {
if (todo.id === payload.id) {
return { ...todo, completed: payload.completed }
}
return todo
})
case ACTIONS.DELETE:
return todos.filter(todo => todo.id !== payload.id)
case ACTIONS.UPDATE:
return todos.map(todo => {
if (todo.id === payload.id) {
return { ...todo, name: payload.name }
}
return todo
})
default:
throw new Error(`No action found for ${type}.`)
}
}
export const TodoContext = createContext()
function App() {
const [filterName, setFilterName] = useState("")
const [hideCompletedFilter, setHideCompletedFilter] = useState(false)
const [todos, dispatch] = useReducer(reducer, [], initialValue => {
const value = localStorage.getItem(LOCAL_STORAGE_KEY)
if (value == null) return initialValue
return JSON.parse(value)
})
const filteredTodos = todos.filter(todo => {
if (hideCompletedFilter && todo.completed) return false
return todo.name.includes(filterName)
})
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todos))
}, [todos])
function addNewTodo(name) {
dispatch({ type: ACTIONS.ADD, payload: { name } })
}
function toggleTodo(todoId, completed) {
dispatch({ type: ACTIONS.TOGGLE, payload: { id: todoId, completed } })
}
function updateTodoName(id, name) {
dispatch({ type: ACTIONS.UPDATE, payload: { id, name } })
}
function deleteTodo(todoId) {
dispatch({ type: ACTIONS.DELETE, payload: { id: todoId } })
}
return (
<TodoContext.Provider
value={{
todos: filteredTodos,
addNewTodo,
toggleTodo,
updateTodoName,
deleteTodo,
}}
>
<TodoFilterForm
name={filterName}
setName={setFilterName}
hideCompleted={hideCompletedFilter}
setHideCompleted={setHideCompletedFilter}
/>
<TodoList />
<NewTodoForm />
</TodoContext.Provider>
)
}
export default App
NewTodoForm.jsx
Raw
#
import { useContext, useRef } from "react"
import { TodoContext } from "./App"
export function NewTodoForm() {
const nameRef = useRef()
const { addNewTodo } = useContext(TodoContext)
function handleSubmit(e) {
e.preventDefault()
if (nameRef.current.value === "") return
addNewTodo(nameRef.current.value)
nameRef.current.value = ""
}
return (
<form onSubmit={handleSubmit} id="new-todo-form">
<label htmlFor="todo-input">New Todo</label>
<input autoFocus type="text" id="todo-input" ref={nameRef} />
<button>Add Todo</button>
</form>
)
}
TodoFilterForm.jsx
Raw
#
export function TodoFilterForm({
name,
setName,
hideCompleted,
setHideCompleted,
}) {
return (
<div className="filter-form">
<div className="filter-form-group">
<label htmlFor="name">Name</label>
<input
type="text"
id="name"
value={name}
onChange={e => setName(e.target.value)}
/>
</div>
<label>
<input
type="checkbox"
checked={hideCompleted}
onChange={e => setHideCompleted(e.target.checked)}
/>
Hide Completed
</label>
</div>
)
}
TodoItem.jsx
Raw
#
import { useContext, useRef, useState } from "react"
import { TodoContext } from "./App"
export function TodoItem({ id, name, completed }) {
const { toggleTodo, deleteTodo, updateTodoName } = useContext(TodoContext)
const [isEditing, setIsEditing] = useState(false)
const nameRef = useRef()
function handleSubmit(e) {
e.preventDefault()
if (nameRef.current.value === "") return
updateTodoName(id, nameRef.current.value)
setIsEditing(false)
}
return (
<li className="list-item">
{isEditing ? (
<form onSubmit={handleSubmit}>
<input autoFocus type="text" defaultValue={name} ref={nameRef} />
<button data-button-edit>Save</button>
</form>
) : (
<>
<label className="list-item-label">
<input
checked={completed}
type="checkbox"
data-list-item-checkbox
onChange={e => toggleTodo(id, e.target.checked)}
/>
<span data-list-item-text>{name}</span>
</label>
<button data-button-edit onClick={() => setIsEditing(true)}>
Edit
</button>
<button onClick={() => deleteTodo(id)} data-button-delete>
Delete
</button>
</>
)}
</li>
)
}
TodoList.jsx
Raw
#
import { useContext } from "react"
import { TodoContext } from "./App"
import { TodoItem } from "./TodoItem"
export function TodoList() {
const { todos } = useContext(TodoContext)
return (
<ul id="list">
{todos.map(todo => {
return <TodoItem key={todo.id} {...todo} />
})}
</ul>
)
}