Skip to content

Instantly share code, notes, and snippets.

@dead-claudia
Created October 29, 2018 21:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dead-claudia/02c7fdf98e42b3d56c48392babac121e to your computer and use it in GitHub Desktop.
Save dead-claudia/02c7fdf98e42b3d56c48392babac121e to your computer and use it in GitHub Desktop.
React if they went with a new language rather than an embedded DSL
// This is a rough port of their TodoMVC app, using their Hooks API
import React, { useRef, useState, useEffect, useMemo } from "react"
import { render } from "react-dom"
function uuid() {
let uuid = ""
for (let i = 0; i < 32; i++) {
if (i === 8 || i === 12 || i === 16 || i === 20) uuid += "-"
if (i === 12) uuid += "4"
else if (i === 16) uuid += (Math.random() * 3 | 8).toString(16)
else uuid += (Math.random() * 16 | 0).toString(16)
}
return uuid
}
const TodoItem = React.memo(props => {
const editField = useRef()
const [editText, setEditText] = useState(props.todo.title)
useEffect(() => {
if (props.editing) {
const node = editField.current
node.focus()
node.setSelectionRange(node.value.length, node.value.length)
}
}, [props.editing])
function onConfirm() {
const newEditText = editText.trim()
if (newEditText) {
props.onSave(newEditText)
setEditText(newEditText)
} else {
props.onDestroy()
}
}
return (
<li className={(
(props.todo.completed ? "completed " : "") +
(props.editing ? "editing " : "")
)}>
<div className="view">
<input
className="toggle"
type="checkbox"
checked={props.todo.completed}
onChange={props.onToggle}
/>
<label onDoubleClick={() => {
setEditText(props.todo.title)
props.onEdit()
}}>
{props.todo.title}
</label>
<button className="destroy" onClick={props.onDestroy} />
</div>
<input
ref={editField}
className="edit"
value={editText}
onBlur={onConfirm}
onChange={ev => {
if (props.editing) setEditText(ev.target.value)
}}
onKeyDown={ev => {
if (ev.which === 27) {
onCancel()
editText <= todo.title
} else if (ev.which !== 13) {
return
}
onConfirm()
}}
/>
</li>
)
})
function TodoFooter({
count, nowShowing, completedCount, onClearCompleted
}) {
const filter = (route, value, label) => <li>
<a href={route} className={
nowShowing === value ? "selected" : undefined
}>
{label}
</a>
</li>
return (
<footer className="footer">
<span className="todo-count">
<strong>{count}</strong> item{count === 1 ? "" : "s"} left
</span>
<ul className="filters">
{filter("#/", "all", "All")}{" "}
{filter("#/active", "active", "Active")}{" "}
{filter("#/completed", "completed", "Completed")}
</ul>
{completedCount ? (
<button
className="clear-completed"
onClick={onClearCompleted}>
Clear completed
</button>
) : null}
</footer>
)
}
const initialTodos = JSON.parse(localStorage["react-todos"] || "[]")
function TodoApp() {
const [nowShowing, setNowShowing] = useState("all")
const [editing, setEditing] = useState(null)
const [newTodo, setNewTodo] = useState("")
const [todos, setTodos] = useState(initialTodos)
const [initialized, setInitialized] = useState(false)
useEffect(
() => localStorage["react-todos"] = JSON.stringify(todos),
[todos]
)
const remove = todo => setTodos(todos.filter(current => current !== todo))
const update = (todo, props) => setTodos(todos.map(current =>
current === todo ? {...current, ...props} : current
))
// Do this synchronously.
if (!initialized) {
setInitialized(true)
new Router({
"/": () => setNowShowing("all"),
"/active": () => setNowShowing("active"),
"/completed": () => setNowShowing("completed"),
}).init("/")
}
const shownTodos = useMemo(() => {
switch (nowShowing) {
case "all": return todos
case "active": return todos.filter(todo => !todo.completed)
case "completed": return todos.filter(todo => todo.completed)
}
}, [todos, nowShowing])
const activeCount = useMemo(
() => todos.reduce((acc, todo) => acc + !todo.completed, 0),
[todos]
)
return (
<div>
<header className="header">
<h1>todos</h1>
<input
className="new-todo"
placeholder="What needs to be done?"
value={newTodo}
onKeyDown={ev => {
if (ev.keyCode !== 13) break
ev.preventDefault()
const title = newTodo.trim()
if (title) {
setTodos([...todos, {id: uuid(), title, completed: false}])
setNewTodo("")
}
}}
onChange={ev => setNewTodo(ev.target.value)}
autoFocus={true}
/>
</header>
{todos.length ? <>
<section className="main">
<input
id="toggle-all"
className="toggle-all"
type="checkbox"
onChange={ev => {
const completed = ev.target.checked
setTodos(todos.map(todo => {...todo, completed}))
}}
checked={activeCount === 0}
/>
<label htmlFor="toggle-all" />
<ul className="todo-list">
{shownTodos.map(todo => <TodoItem
key={todo.id}
todo={todo}
editing={editing === todo.id}
onToggle={() => {
update(todo, {completed: !todo.completed})
}}
onDestroy={() => remove(todo)}
onEdit={() => setEditing(todo.id)}
onSave={title => {
update(todo, {title})
setEditing(null)
}}
onCancel={() => setEditing(null)}
/>)}
</ul>
</section>
<TodoFooter
count={activeCount}
completedCount={todos.length - activeCount}
nowShowing={nowShowing}
onClearCompleted={() => {
setTodos(todos.filter(todo => !todo.completed))
}}
/>
</> : null}
</div>
)
}
React.render(<TodoApp />, document.getElementsByClassName("todoapp")[0])
// This is a rough port of their TodoMVC app, using a hypothetical DSL
function uuid() {
let uuid = ""
for (let i = 0; i < 32; i++) {
if (i === 8 || i === 12 || i === 16 || i === 20) uuid += "-"
if (i === 12) uuid += "4"
else if (i === 16) uuid += (Math.random() * 3 | 8).toString(16)
else uuid += (Math.random() * 16 | 0).toString(16)
}
return uuid
}
component <TodoItem todo editing onEdit onCancel onSave onDestroy onToggle /> {
static const editField = {current: undefined}
cell editText = todo.title
effect {
if (editing) {
const node = editField.current
node.focus()
node.setSelectionRange(node.value.length, node.value.length)
}
}
function onConfirm() {
const newEditText = editText.trim()
if (newEditText) {
onSave(newEditText)
editText <= newEditText
} else {
onDestroy()
}
}
return (
<li className={(
(todo.completed ? "completed " : "") +
(editing ? "editing " : "")
)}>
<div className="view">
<input
className="toggle"
type="checkbox"
checked={todo.completed}
onChange={onToggle}
/>
<label onDoubleClick={() => {
editText <= todo.title
onEdit()
}}>{todo.title}</label>
<button className="destroy" onClick={onDestroy} />
</div>
<input
ref={editField}
className="edit"
value={editText}
onBlur={onConfirm}
onChange={ev => { if (editing) editText <= ev.target.value }}
onKeyDown={ev => {
if (ev.which === 27) {
onCancel()
editText <= todo.title
} else if (ev.which !== 13) {
return
}
onConfirm()
}}
/>
</li>
)
}
component <TodoFooter count nowShowing completedCount onClearCompleted /> {
component <Filter route value label /> {
return <li>
<a href={route} className={
nowShowing === value ? "selected" : undefined
}>
{label}
</a>
</li>
}
return (
<footer className="footer">
<span className="todo-count">
<strong>{count}</strong> item{count === 1 ? "" : "s"} left
</span>
<ul className="filters">
<Filter route="#/" value="all" label="All" />{" "}
<Filter route="#/active" value="active" label="Active" />{" "}
<Filter route="#/completed" value="completed" label="Completed" />
</ul>
{completedCount ? (
<button
className="clear-completed"
onClick={onClearCompleted}>
Clear completed
</button>
) : null}
</footer>
)
}
component <TodoApp /> {
cell nowShowing = "all"
cell editing = null
cell newTodo = ""
cell todos = JSON.parse(localStorage["react-todos"] || "[]")
cell initialized = false
effect {
localStorage["react-todos"] = JSON.stringify(todos)
}
const update = (todo, props) => todos <= todos.map(current =>
current === todo ? {...current, ...props} : current
)
// Do this synchronously.
if (!initialized) {
initialized <= true
new Router({
"/": () => nowShowing <= "all",
"/active": () => nowShowing <= "active",
"/completed": () => nowShowing <= "completed",
}).init("/")
}
const shownTodos = use memo([todos, nowShowing], () => {
switch (nowShowing) {
case "all": return todos
case "active": return todos.filter(todo => !todo.completed)
case "completed": return todos.filter(todo => todo.completed)
}
})
const activeCount = use memo([todos], () =>
todos.reduce((acc, todo) => acc + !todo.completed, 0)
)
return (
<div>
<header className="header">
<h1>todos</h1>
<input
className="new-todo"
placeholder="What needs to be done?"
value={newTodo}
onKeyDown={ev => {
if (ev.keyCode !== 13) break
ev.preventDefault()
const title = newTodo.trim()
if (title) {
todos <= [...todos, {id: uuid(), title, completed: false}]
newTodo <= ""
}
}}
onChange={ev => newTodo <= ev.target.value}
autoFocus={true}
/>
</header>
{todos.length ? <>
<section className="main">
<input
id="toggle-all"
className="toggle-all"
type="checkbox"
onChange={ev => {
const completed = ev.target.checked
todos <= todos.map(todo => {...todo, completed})
}}
checked={activeCount === 0}
/>
<label htmlFor="toggle-all" />
<ul className="todo-list">
{shownTodos.map(todo => <TodoItem
key={todo.id}
todo={todo}
editing={editing === todo.id}
onToggle={() => {
update(todo, {completed: !todo.completed})
}}
onDestroy={() => {
todos <= todos.filter(t => t !== todo)
}}
onEdit={() => editing <= todo.id}
onSave={title => {
update(todo, {title})
editing <= null
}}
onCancel={() => editing <= null}
/>)}
</ul>
</section>
<TodoFooter
count={activeCount}
completedCount={todos.length - activeCount}
nowShowing={nowShowing}
onClearCompleted={() => {
todos <= todos.filter(todo => !todo.completed)
}}
/>
</> : null}
</div>
)
}
render(document.querySelector(".todoapp")) { return <TodoApp /> }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment