const validate = createEffect()
const submit = createEvent()
const submitted = createEvent()
const completed = createEvent()
const changed = createEvent()
const removed = createEvent()
const $todos = createStore([])
const $todo = restore(changed, '').reset(submitted)
const $error = restore(validate.failData, '').reset(changed)
submit.watch(e => e.preventDefault())
$todos.on(submitted, (prev, next) =>
prev.concat({text: next, completed: false})
$todos.on(completed, (state, index) =>
state.map((item, i) => ({
completed: index === i ? !item.completed : item.completed,
$todos.on(removed, (state, index) => state.filter((_, i) => i !== index))
validate.use(([todo, todos]) => {
if (todos.some(item => item.text === todo))
throw 'This todo is already on the list'
if (!todo.trim().length) throw 'Required field'
source: combine($todo, $todos),
const tasks = useStore($todos)
const todo = useStore($todo)
const error = useStore($error)
const list = useList($todos, (item, index) => (
<li style={{textDecoration: item.completed ? 'line-through' : ''}}>
onChange={() => completed(index)}
<button type="button" onClick={() => removed(index)} className="delete">
onChange={e => changed(e.target.value)}
<button type="submit" onClick={submit} className="submit">
{error && <div className="error">{error}</div>}
<ul style={{listStyle: 'none'}}>{list}</ul>
ReactDOM.render(<App />, document.getElementById('root'))
const style = document.createElement('style')
document.body.appendChild(style)
grid-template-columns: 3fr 1fr;
border: 1px solid #24292E;
background-color: #24292E;
border: 1px solid #24292E;
border-top: 1px solid #24292E;
transform: translateY(-50%);