<html lang="en">
<head></head>
<body>

<form id="mainForm" method="post" action="https://stackblitz.com/run" target="_self">
<input type="hidden" name="project[files][README.md]" value="# Todos [![Open in StackBlitz](https://img.shields.io/badge/Open%20in-StackBlitz-blue?style=flat-square&amp;logo=stackblitz)](https://stackblitz.com/github/pmndrs/jotai/tree/main/examples/todos)

## Description

Create a todo list, mark tasks as completed, and switch between `Completed` and `Incomplete` to filter items.

## Set up locally

```bash
git clone https://github.com/pmndrs/jotai

# install project dependencies &amp; build the library
cd jotai &amp;&amp; pnpm install

# move to the examples folder &amp; install dependencies
cd examples/todos &amp;&amp; pnpm install

# start the dev server
pnpm dev
```

## Set up on `StackBlitz`

Link: https://stackblitz.com/github/pmndrs/jotai/tree/main/examples/todos
">
<input type="hidden" name="project[files][index.html]" value="&lt;!doctype html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
    &lt;meta name=&quot;theme-color&quot; content=&quot;#000000&quot; /&gt;
    &lt;title&gt;Jotai Examples | Todos&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
    &lt;script type=&quot;module&quot; src=&quot;/src/index.tsx&quot;&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
">
<input type="hidden" name="project[files][package.json]" value="{&quot;name&quot;:&quot;todos&quot;,&quot;version&quot;:&quot;2.0.0&quot;,&quot;description&quot;:&quot;Create and filter a todo list with Jotai&quot;,&quot;type&quot;:&quot;module&quot;,&quot;scripts&quot;:{&quot;dev&quot;:&quot;vite&quot;,&quot;build&quot;:&quot;tsc &amp;&amp; vite build&quot;,&quot;serve&quot;:&quot;vite preview&quot;},&quot;dependencies&quot;:{&quot;@ant-design/icons&quot;:&quot;^5.5.2&quot;,&quot;@react-spring/web&quot;:&quot;^9.2.3&quot;,&quot;antd&quot;:&quot;^4.16.2&quot;,&quot;jotai&quot;:&quot;https://pkg.pr.new/pmndrs/jotai/jotai@bc29c31&quot;,&quot;react&quot;:&quot;^18.2.0&quot;,&quot;react-dom&quot;:&quot;^18.2.0&quot;},&quot;devDependencies&quot;:{&quot;@types/react&quot;:&quot;^18.2.0&quot;,&quot;@types/react-dom&quot;:&quot;^18.2.0&quot;,&quot;@vitejs/plugin-react&quot;:&quot;^4.3.4&quot;,&quot;typescript&quot;:&quot;^5.0.0&quot;,&quot;vite&quot;:&quot;^6.0.5&quot;}}">
<input type="hidden" name="project[files][tsconfig.json]" value="{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es2019&quot;,
    &quot;strict&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;allowSyntheticDefaultImports&quot;: true,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;lib&quot;: [&quot;dom&quot;, &quot;dom.iterable&quot;, &quot;esnext&quot;],
    &quot;jsx&quot;: &quot;react-jsx&quot;
  },
  &quot;include&quot;: [&quot;./src/**/*&quot;],
  &quot;exclude&quot;: [&quot;node_modules&quot;]
}
">
<input type="hidden" name="project[files][vite.config.ts]" value="import { defineConfig } from &#39;vite&#39;
import react from &#39;@vitejs/plugin-react&#39;

export default defineConfig({
  plugins: [react()],
})
">
<input type="hidden" name="project[files][src/App.tsx]" value="import type { FormEvent } from &#39;react&#39;
import { CloseOutlined } from &#39;@ant-design/icons&#39;
import { a, useTransition } from &#39;@react-spring/web&#39;
import { Radio } from &#39;antd&#39;
import { Provider, atom, useAtom, useSetAtom } from &#39;jotai&#39;
import type { PrimitiveAtom } from &#39;jotai&#39;

type Todo = {
  title: string
  completed: boolean
}

const filterAtom = atom(&#39;all&#39;)
const todosAtom = atom&lt;PrimitiveAtom&lt;Todo&gt;[]&gt;([])
const filteredAtom = atom&lt;PrimitiveAtom&lt;Todo&gt;[]&gt;((get) =&gt; {
  const filter = get(filterAtom)
  const todos = get(todosAtom)
  if (filter === &#39;all&#39;) return todos
  else if (filter === &#39;completed&#39;)
    return todos.filter((atom) =&gt; get(atom).completed)
  else return todos.filter((atom) =&gt; !get(atom).completed)
})

type RemoveFn = (item: PrimitiveAtom&lt;Todo&gt;) =&gt; void
type TodoItemProps = {
  atom: PrimitiveAtom&lt;Todo&gt;
  remove: RemoveFn
}
const TodoItem = ({ atom, remove }: TodoItemProps) =&gt; {
  const [item, setItem] = useAtom(atom)
  const toggleCompleted = () =&gt;
    setItem((props) =&gt; ({ ...props, completed: !props.completed }))
  return (
    &lt;&gt;
      &lt;input
        type=&quot;checkbox&quot;
        checked={item.completed}
        onChange={toggleCompleted}
      /&gt;
      &lt;span style={{ textDecoration: item.completed ? &#39;line-through&#39; : &#39;&#39; }}&gt;
        {item.title}
      &lt;/span&gt;
      &lt;CloseOutlined onClick={() =&gt; remove(atom)} /&gt;
    &lt;/&gt;
  )
}

const Filter = () =&gt; {
  const [filter, set] = useAtom(filterAtom)
  return (
    &lt;Radio.Group onChange={(e) =&gt; set(e.target.value)} value={filter}&gt;
      &lt;Radio value=&quot;all&quot;&gt;All&lt;/Radio&gt;
      &lt;Radio value=&quot;completed&quot;&gt;Completed&lt;/Radio&gt;
      &lt;Radio value=&quot;incompleted&quot;&gt;Incomplete&lt;/Radio&gt;
    &lt;/Radio.Group&gt;
  )
}

type FilteredType = {
  remove: RemoveFn
}
const Filtered = (props: FilteredType) =&gt; {
  const [todos] = useAtom(filteredAtom)
  const transitions = useTransition(todos, {
    keys: (todo) =&gt; todo.toString(),
    from: { opacity: 0, height: 0 },
    enter: { opacity: 1, height: 40 },
    leave: { opacity: 0, height: 0 },
  })
  return transitions((style, atom) =&gt; (
    &lt;a.div className=&quot;item&quot; style={style}&gt;
      &lt;TodoItem atom={atom} {...props} /&gt;
    &lt;/a.div&gt;
  ))
}

const TodoList = () =&gt; {
  // Use `useSetAtom` to avoid re-render
  // const [, setTodos] = useAtom(todosAtom)
  const setTodos = useSetAtom(todosAtom)
  const remove: RemoveFn = (todo) =&gt;
    setTodos((prev) =&gt; prev.filter((item) =&gt; item !== todo))
  const add = (e: FormEvent&lt;HTMLFormElement&gt;) =&gt; {
    e.preventDefault()
    const title = e.currentTarget.inputTitle.value
    e.currentTarget.inputTitle.value = &#39;&#39;
    setTodos((prev) =&gt; [...prev, atom&lt;Todo&gt;({ title, completed: false })])
  }
  return (
    &lt;form onSubmit={add}&gt;
      &lt;Filter /&gt;
      &lt;input name=&quot;inputTitle&quot; placeholder=&quot;Type ...&quot; /&gt;
      &lt;Filtered remove={remove} /&gt;
    &lt;/form&gt;
  )
}

export default function App() {
  return (
    &lt;Provider&gt;
      &lt;h1&gt;Jōtai&lt;/h1&gt;
      &lt;TodoList /&gt;
    &lt;/Provider&gt;
  )
}
">
<input type="hidden" name="project[files][src/index.tsx]" value="import { createRoot } from &#39;react-dom/client&#39;
import &#39;antd/dist/antd.css&#39;
import &#39;./styles.css&#39;
import App from &#39;./App&#39;

const rootElement = document.getElementById(&#39;root&#39;)
createRoot(rootElement!).render(&lt;App /&gt;)
">
<input type="hidden" name="project[files][src/styles.css]" value="@import url(&#39;https://rsms.me/inter/inter.css&#39;);

* {
  box-sizing: border-box;
}

html,
body {
  width: 100%;
  height: 100%;
}

body {
  margin-top: 5em;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  background: #fdfdfd;
  font-family: &#39;Inter&#39;, sans-serif !important;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  filter: saturate(0);
}

#root {
  width: 50ch;
  display: flex;
  flex-direction: column;
  gap: 1em;
}

input:not([type=&#39;checkbox&#39;]) {
  width: 100%;
  border: none;
  box-shadow: 0px 15px 30px rgba(0, 0, 0, 0.05);
  padding: 10px 20px;
  margin-top: 2em;
  margin-bottom: 4em;
  background: white;
}

input:focus {
  outline: none;
}

.anticon-close {
  width: 32px !important;
  cursor: pointer;
  color: #c0c0c0;
}

.anticon-close:hover {
  color: #272730;
}

.item {
  position: relative;
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  overflow: hidden;
}

.item &gt; span {
  display: inline-block;
  width: 100%;
}

h1 {
  font-size: 10em;
  font-weight: 800;
  margin: 0;
  padding: 0;
  letter-spacing: -5px;
  color: black;
  white-space: nowrap;
}
">
<input type="hidden" name="project[files][src/vite-env.d.ts]" value="/// &lt;reference types=&quot;vite/client&quot; /&gt;
">
<input type="hidden" name="project[files][public/index.html]" value="&lt;!doctype html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot; /&gt;
    &lt;meta
      name=&quot;viewport&quot;
      content=&quot;width=device-width, initial-scale=1, shrink-to-fit=no&quot;
    /&gt;
    &lt;meta name=&quot;theme-color&quot; content=&quot;#000000&quot; /&gt;
    &lt;!--
      manifest.json provides metadata used when your web app is added to the
      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
    --&gt;
    &lt;link rel=&quot;manifest&quot; href=&quot;%PUBLIC_URL%/manifest.json&quot; /&gt;
    &lt;link rel=&quot;shortcut icon&quot; href=&quot;%PUBLIC_URL%/favicon.ico&quot; /&gt;
    &lt;!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike &quot;/favicon.ico&quot; or &quot;favicon.ico&quot;, &quot;%PUBLIC_URL%/favicon.ico&quot; will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    --&gt;
    &lt;title&gt;Jotai Examples | Todos&lt;/title&gt;
  &lt;/head&gt;

  &lt;body&gt;
    &lt;noscript&gt; You need to enable JavaScript to run this app. &lt;/noscript&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
    &lt;!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the &lt;body&gt; tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    --&gt;
  &lt;/body&gt;
&lt;/html&gt;
">
<input type="hidden" name="project[description]" value="generated by https://pkg.pr.new">
<input type="hidden" name="project[template]" value="node">
<input type="hidden" name="project[title]" value="todos">
</form>
<script>document.getElementById("mainForm").submit();</script>

</body></html>