useReducer demo

This commit is contained in:
Joshua Seigler 2022-10-10 01:30:32 -04:00
parent de3f50b388
commit 31a9a1b463
3 changed files with 165 additions and 17 deletions

View file

@ -1,3 +1,7 @@
import { UseReducer } from "./useReducer";
import { UseState } from "./useState"; import { UseState } from "./useState";
export const strategies = [{ name: 'useState', component: UseState }] export const strategies = [
{ name: 'useState', component: UseState },
{ name: 'useReducer', component: UseReducer },
]

View file

@ -0,0 +1,145 @@
import React, { useEffect, useReducer, useState } from 'react'
import localforage from 'localforage'
import { useAsyncValue } from 'react-router'
type Todo = {
id: string
text: string
status: 'incomplete' | 'complete'
}
type TodoAction =
| {
type: 'delete'
value: string
}
| {
type: 'add'
value: Todo
}
| {
type: 'replace'
value: Todo[]
}
| {
type: 'update'
value: Todo
}
const reducer = (state: Todo[], action: TodoAction) => {
const { type, value } = action
switch (type) {
case 'delete':
return state.filter((todo) => todo.id !== value)
case 'add':
return state.concat([value])
case 'replace':
return value
case 'update':
return state.map((todo) => (todo.id === value.id ? value : todo))
}
}
export function UseReducer() {
const [isLoading, setLoading] = useState(true)
const [todos, dispatchTodoAction] = useReducer(reducer, [])
const [newTodoText, setNewTodoText] = useState('')
useEffect(() => {
// run once when mounted
localforage.getItem('react-state-management/todos', (_err, value) => {
if (value) {
dispatchTodoAction({ type: 'replace', value: value as Todo[] }) // validation first would be better
}
setLoading(false)
})
}, [])
useEffect(() => {
// keep local db up to date
localforage.setItem('react-state-management/todos', todos)
}, [todos])
function addTodo() {
const newTodo = {
id: crypto.randomUUID(),
text: newTodoText,
status: 'incomplete' as const
}
dispatchTodoAction({
type: 'add',
value: newTodo
})
setNewTodoText('')
}
return (
<main style={isLoading ? { pointerEvents: 'none', cursor: 'wait' } : {}}>
<TodoList todos={todos} dispatch={dispatchTodoAction} />
<form
onSubmit={(e) => {
e.preventDefault()
addTodo()
}}>
<label>
New todo:{' '}
<input
type="text"
value={newTodoText}
onChange={(e) => setNewTodoText(e.target.value)}
/>
</label>
</form>
</main>
)
}
function TodoList({
todos,
dispatch
}: {
todos: Todo[]
dispatch: (TodoAction) => void
}) {
return (
<>
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} dispatch={dispatch} />
))}
</>
)
}
function TodoItem({
todo,
dispatch
}: {
todo: Todo
dispatch: (TodoAction) => void
}) {
return (
<label
style={
todo.status === 'complete' ? { textDecoration: 'line-through' } : {}
}>
<a
style={{ float: 'right' }}
onClick={() => dispatch({ type: 'delete', value: todo.id })}>
x
</a>
<input
type="checkbox"
onChange={(e) => {
dispatch({
type: 'update',
value: {
...todo,
status: e.target.checked ? 'complete' : 'incomplete'
}
})
}}
checked={todo.status === 'complete'}
/>{' '}
{todo.text}
</label>
)
}

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react' import React, { useEffect, useState } from 'react'
import localforage from 'localforage' import localforage from 'localforage'
type Todo = { type Todo = {
@ -12,21 +12,20 @@ export function UseState() {
const [todos, setTodos] = useState<Todo[]>([]) const [todos, setTodos] = useState<Todo[]>([])
const [newTodoText, setNewTodoText] = useState('') const [newTodoText, setNewTodoText] = useState('')
localforage.getItem('react-state-management/todos', (_err, value) => { useEffect(() => {
if (value) { // run once when mounted
setTodos(value as Todo[]) // validation first would be better localforage.getItem('react-state-management/todos', (_err, value) => {
} if (value) {
setLoading(false) setTodos(value as Todo[]) // validation first would be better
}) }
function updateTodos(newTodos: Todo[]) {
// alternative to tricky useEffect
setLoading(true)
setTodos(newTodos)
localforage.setItem('react-state-management/todos', newTodos).then(() => {
setLoading(false) setLoading(false)
}) })
} }, [])
useEffect(() => {
// keep local db up to date
localforage.setItem('react-state-management/todos', todos)
}, [todos])
function addTodo() { function addTodo() {
const newTodo = { const newTodo = {
@ -36,11 +35,11 @@ export function UseState() {
} }
const newTodos = [...todos, newTodo] const newTodos = [...todos, newTodo]
setNewTodoText('') setNewTodoText('')
updateTodos(newTodos) setTodos(newTodos)
} }
function todoSetter(id: string, newValue?: Todo) { function todoSetter(id: string, newValue?: Todo) {
updateTodos( setTodos(
todos.reduce((acc, cur) => { todos.reduce((acc, cur) => {
if (cur.id === id) { if (cur.id === id) {
if (newValue === undefined) { if (newValue === undefined) {