Skip to content

Instantly share code, notes, and snippets.

@zaydek-old
Created August 12, 2019 16:38
Show Gist options
  • Save zaydek-old/3ba64d6679e82a43a7f93e29785a8998 to your computer and use it in GitHub Desktop.
Save zaydek-old/3ba64d6679e82a43a7f93e29785a8998 to your computer and use it in GitHub Desktop.
import React, { useEffect, useRef } from "react"
import useMethods from "use-methods"
const setLocalStorage = (key: string, value: Object) => {
const json = JSON.stringify(value)
localStorage.setItem(key, json)
}
const getLocalStorage = (key: string) => {
const json = localStorage.getItem(key)
if (json === null) {
return null
}
return JSON.parse(json)
}
const shortID = () => {
// See gist.github.com/gordonbrander/2230317 for reference.
return Math.random().toString(36).substr(2, 4)
}
type State = {
focus: boolean,
input: string,
todos: {
id: string,
value: string,
complete: boolean
}[],
saved: boolean,
// The undo/redo state stack and index.
stack: [],
index: 0
}
const initialState: State = {
focus: false,
input: "",
todos: [],
saved: true,
// The udno/redo state stack and index.
stack: [],
index: 0
}
// selected
// undo
// redo
// if (todo.complete) {
// state.count.push(todo.id)
// } else {
// const countIndex = state.count.findIndex(cmpTodo => todo.id == cmpTodo.id)
// if (countIndex !== -1) {
// state.count.splice(countIndex, 1)
// }
// }
const methods = (state: State) => ({
clearAll() {
return initialState
},
focus() {
state.focus = true
},
blur() {
state.focus = false
},
input(value: string) {
state.input = value
},
addTodo() {
if (state.focus && state.input) {
state.todos.unshift({id: shortID(), value: state.input, complete: false})
state.input = ""
}
},
toggleTodo(id: string) {
const todo = state.todos.find(todo => id === todo.id)
if (todo) {
todo.complete = !todo.complete
}
},
updateTodo(id: string, value: string) {
const todo = state.todos.find(todo => id === todo.id)
if (todo) {
todo.value = value
}
},
deleteTodo(id: string) {
const index = state.todos.findIndex(todo => id === todo.id)
if (index !== -1) {
state.todos.splice(index, 1)
}
},
deleteAllTodos() {
// Use `splice` backwards to avoid destructive resizing.
// See djave.co.uk/blog/read/splice-doesnt-work-very-well-in-a-javascript-for-loop for reference.
for (let index = state.todos.length; index >= 0; index--) {
const todo = state.todos[index]
if (todo && todo.complete) {
state.todos.splice(index, 1)
}
}
},
saving() {
state.saved = false
},
saved() {
state.saved = true
},
undo() {
},
redo() {
}
})
// See github.com/streamich/react-use/blob/master/src/useUpdateEffect.ts for reference.
const useUpdateEffect: typeof useEffect = (effect, deps) => {
const didEffect = useRef(false)
useEffect(
!didEffect.current
? () => {
didEffect.current = true
}
: effect,
deps
)
}
// See github.com/streamich/react-use/blob/master/src/useDebounce.ts for reference.
const useDebounce = (fn: () => any, ms: number = 0, deps: any[] = []) => {
useUpdateEffect(() => {
const debounce = setTimeout(fn.bind(null, deps), ms)
return () => {
clearTimeout(debounce)
}
}, deps)
}
const App = (props: { children?: any }) => {
const [state, dispatch] = useMethods(methods, getLocalStorage("todo-app") || initialState)
const stateRef = useRef(state)
stateRef.current = state
useUpdateEffect(() => {
dispatch.saving()
}, [state.input, state.todos])
useDebounce(() => {
dispatch.saved()
setLocalStorage("todo-app", stateRef.current)
}, 1e3, [state.input, state.todos])
const handleClear = (e: React.FormEvent<EventTarget>) => {
e.preventDefault()
const sure = window.confirm("Are you sure?")
if (sure) {
dispatch.clearAll()
}
}
const handleSubmit = (e: React.FormEvent<EventTarget>) => {
e.preventDefault()
dispatch.addTodo()
}
const completed = state.todos.reduce((arr, todo, index) => {
if (todo.complete) {
arr.push(index)
}
return arr
}, [] as number[]) // TS requires `as number[]`.
// const a = state.todos.reduce((arr, todo, index) => {
// if (todo.complete) {
// arr.push(index)
// }
// return arr
// }, ([]: number[]))
return (
<div>
<form onSubmit={handleSubmit}>
{/* Use `<input type="button" />` instead of <button type="submit" />. */}
<input type="button" value="Clear all" onClick={handleClear} />
<input type="text" value={state.input} onFocus={dispatch.focus} onChange={e => dispatch.input(e.target.value)} />
<button type="submit">Add</button>
<input type="button" value={"Delete all" + (!completed.length ? "" : " " + completed.length)} onClick={dispatch.deleteAllTodos} />
Saved status: {"" + state.saved}
{state.todos && (
<ul>
{/* {all.length} */}
{state.todos.map(({id, value, complete}) => (
<li key={id}>
<input type="checkbox" checked={complete} onChange={() => dispatch.toggleTodo(id)} />
<input style={{textDecoration: !complete ? "none" : "line-through"}} type="text" value={value} onFocus={dispatch.blur} onChange={e => dispatch.updateTodo(id, e.target.value)} />
<button onClick={() => dispatch.deleteTodo(id)}>Delete</button>
</li>
))}
</ul>
)}
</form>
</div>
)
}
export default App
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment