Skip to content

Instantly share code, notes, and snippets.

@mrjjwright
Created December 3, 2024 03:43
Show Gist options
  • Save mrjjwright/520cc4fe6ea212f8aaa2b20911264256 to your computer and use it in GitHub Desktop.
Save mrjjwright/520cc4fe6ea212f8aaa2b20911264256 to your computer and use it in GitHub Desktop.
import { Effect, PubSub, Queue } from 'effect'
import React from 'react'
import { createRoot } from 'react-dom/client'
const publish = <T,>(pubSub: PubSub.PubSub<T>) => {
Effect.runSync(PubSub.publish(pubSub, Date.now() as T))
}
function actOnMessageValue<T, E>(
pubSub: PubSub.PubSub<number>,
onMessage: Effect.Effect<T, E, never>,
action: (value: T) => void
) {
return Effect.scoped(
Effect.gen(function* (_) {
const dequeue = yield* PubSub.subscribe(pubSub)
while (true) {
yield* Queue.take(dequeue)
const messageValue = yield* onMessage
console.log('messageValue', messageValue)
action(messageValue)
}
})
)
}
export const db = window.db
// Schema
db.exec(`
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
completed BOOLEAN DEFAULT FALSE
);
CREATE TABLE IF NOT EXISTS app_state (
key TEXT PRIMARY KEY,
value TEXT
);
-- Test data
INSERT OR IGNORE INTO todos (id, title, completed) VALUES
(1, 'Learn TypeScript', 1),
(2, 'Build an app', 0),
(3, 'Write tests', 0);
`)
// Types
interface Todo {
id: number
title: string
completed: boolean
}
const todoPubSub = Effect.runSync(PubSub.unbounded<number>())
// Database operations
const op = {
selectTodos: Effect.gen(function* (_) {
const filterRow = yield* op.selectFilter
const query = filterRow
? db
.prepare('SELECT * FROM todos WHERE LOWER(title) LIKE LOWER(?)')
.all('%' + filterRow + '%')
: db.prepare('SELECT * FROM todos').all()
return query as Todo[]
}),
selectFilter: Effect.try(() => {
const row = db.prepare('SELECT value FROM app_state WHERE key = ?').get('filter') as
| { value: string }
| undefined
return row?.value || ''
}),
insertOrReplaceFilter: (filter: string) =>
Effect.try(() => {
db.prepare('INSERT OR REPLACE INTO app_state (key, value) VALUES (?, ?)').run(
'filter',
filter
)
return filter
}),
insertTodo: (title: string) =>
Effect.try(() => {
const result = db
.prepare('INSERT INTO todos (title, completed) VALUES (?, ?) RETURNING *')
.get(title, 0)
return result as Todo
})
}
// UI Components
const ui = {
TodoList: ({ todos }: { todos: Todo[] }) => (
<table>
<thead>
<tr>
<th>Title</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{todos.map((todo) => (
<tr key={todo.id}>
<td>{todo.title}</td>
<td>{todo.completed ? 'Done' : 'Todo'}</td>
</tr>
))}
</tbody>
</table>
),
AddTodoForm: ({ onSubmit }: { onSubmit: (title: string) => void }) => {
const [input, setInput] = React.useState('')
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
const title = input.trim()
if (title) {
onSubmit(title)
setInput('')
}
}
return (
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Enter new todo..."
/>
<button type="submit">Add Todo</button>
</form>
)
},
TodoContainer: () => {
const state = {
todos: React.useState<Todo[]>([]),
filter: React.useState('')
}
React.useEffect(() => {
Effect.runFork(actOnMessageValue(todoPubSub, op.selectTodos, state.todos[1]))
}, [])
React.useEffect(() => publish(todoPubSub), [])
const handleFilterChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newFilter = e.target.value
state.filter[1](newFilter)
Effect.runPromise(op.insertOrReplaceFilter(newFilter)).then(() => {
publish(todoPubSub)
})
}
const handleAddTodo = (title: string) => {
Effect.runPromise(Effect.andThen(op.insertTodo(title), () => publish(todoPubSub)))
}
return (
<div>
<input
value={state.filter[0]}
onChange={handleFilterChange}
placeholder="Filter todos..."
/>
<ui.TodoList todos={state.todos[0]} />
<ui.AddTodoForm onSubmit={handleAddTodo} />
</div>
)
},
mount: (root: HTMLElement) => {
root.innerHTML = ''
const container = createRoot(root)
container.render(<ui.TodoContainer />)
}
}
// Initialize app
const program = Effect.succeed(document.getElementById('root')).pipe(
Effect.map((root) => root && ui.mount(root))
)
Effect.runPromise(program).catch(console.error)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment