add useContext

This commit is contained in:
Joshua Seigler 2022-10-10 16:31:33 -04:00
parent 9d42cbf352
commit 60684dbd0a
No known key found for this signature in database
3 changed files with 159 additions and 3 deletions

View file

@ -52,7 +52,13 @@ function Introduction() {
return (
<main>
<h2>Introduction</h2>
<p>Here are some ways to store state in a React Application.</p>
<p>
This repo has a simple ToDo app mostly using React's <code>useState</code> hook.
</p>
<p>
We will rewrite this app with <code>useReducer</code>, <code>Redux</code>, <code>useContext</code>, and
something new, Preact <code>signal</code>s.
</p>
</main>
)
}

View file

@ -1,9 +1,11 @@
import { UseReducer } from "./useReducer";
import { UseSignal } from "./useSignal";
import { UseState } from "./useState";
import { UseReducer } from "./useReducer";
import { UseContext } from "./useContext";
import { UseSignal } from "./useSignal";
export const strategies = [
{ name: 'useState', component: UseState },
{ name: 'useReducer', component: UseReducer },
{ name: 'useContext', component: UseContext },
{ name: 'useSignal', component: UseSignal },
]

View file

@ -0,0 +1,148 @@
import React, { useContext, useEffect, useReducer, useState } from 'react'
import localforage from 'localforage'
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))
}
}
const TodoContext = React.createContext({
todos: [] as Todo[],
dispatchTodoAction: (TodoAction) => {}
})
export function UseContext() {
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' } : {}}>
<TodoContext.Provider value={{
todos,
dispatchTodoAction
}}>
<TodoList />
<form
onSubmit={(e) => {
e.preventDefault()
addTodo()
}}>
<label>
New todo:{' '}
<input
type="text"
value={newTodoText}
onChange={(e) => setNewTodoText(e.target.value)}
/>
</label>
</form>
</TodoContext.Provider>
</main>
)
}
function TodoList() {
const { todos } = useContext(TodoContext)
return (
<>
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} />
))}
</>
)
}
function TodoItem({
todo
}: {
todo: Todo
}) {
const { dispatchTodoAction: dispatch } = useContext(TodoContext)
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>
)
}