Skip to content

Instantly share code, notes, and snippets.

@ryanflorence
Last active December 8, 2021 20:33
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ryanflorence/89b16f9e1b115f08f6ff4d1e337a5ca2 to your computer and use it in GitHub Desktop.
Save ryanflorence/89b16f9e1b115f08f6ff4d1e337a5ca2 to your computer and use it in GitHub Desktop.
import type { Action, Loader } from "@remix-run/loader";
import { json, parseFormBody, redirect } from "@remix-run/loader";
import { readTodos, createTodo, deleteTodo } from "../data/todo";
let action: Action = async ({ request, context: { session } }) => {
let [method, body] = await methodOverride(request);
await new Promise((res) => setTimeout(res, 1000));
switch (method) {
case "post": {
let [_, error] = await createTodo(body!.name);
if (error) {
session.set("error", error);
}
return redirect("/todos");
}
case "delete": {
await deleteTodo(body!.id);
return redirect("/todos");
}
default: {
throw new Error(`Unknown method! ${method}`);
}
}
};
let loader: Loader = async ({ context: { session } }) => {
let todos = await readTodos();
let error = session.consume("error");
return json({ todos, error: error || null });
};
async function methodOverride(
request: Request
): Promise<["get", null] | [string, { [key: string]: any }]> {
let method = request.method.toLowerCase();
if (method === "get") {
return ["get", null];
}
let body = await parseFormBody(request);
if (method === "post" && body._method) {
method = body._method.toLowerCase();
}
return [method, body];
}
export { loader, action };
import React from "react";
import { useRouteData, Form, usePendingFormSubmit } from "@remix-run/react";
import type { Todo } from "../../loaders/data/todo";
export function headers() {
return {
"cache-control": "no-cache",
};
}
export function meta() {
return {
title: "Todos Yo",
description: "You have to have a todo list, right?",
};
}
export default function Todos() {
let { todos, error } = useRouteData();
let ref = React.useRef<null | HTMLInputElement>(null);
let pendingForm = usePendingFormSubmit();
let state =
pendingForm?.method === "post"
? "creating"
: pendingForm?.method === "delete"
? "deleting"
: "idle";
let showErrorTodo = state === "idle" && error;
let pendingTodo = pendingForm
? Object.fromEntries(pendingForm.data)
: undefined;
return (
<div>
<h2>Todos</h2>
<Form
method="post"
onSubmit={(event) => {
if (pendingForm) {
event.preventDefault();
} else {
requestAnimationFrame(() => {
ref.current!.value = "";
});
}
}}
>
<input ref={ref} name="name" />
</Form>
<ul style={{ lineHeight: "2" }}>
{showErrorTodo && (
<li>
<span style={{ opacity: 0.5 }}>{error.name}</span>{" "}
<span style={{ color: "red" }}>{error.message}</span>
</li>
)}
{state === "creating" && (
<Delayed ms={200}>
<li style={{ opacity: 0.5 }}>{pendingTodo!.name}</li>
</Delayed>
)}
{todos.map((todo: Todo) => (
<li
key={todo.id}
style={{
opacity:
state === "deleting" && pendingTodo!.id === todo.id ? 0.25 : 1,
}}
>
{todo.name}{" "}
<DeleteButton
id={todo.id}
disabled={state === "deleting" || state === "creating"}
/>
</li>
))}
</ul>
</div>
);
}
function DeleteButton({
id,
disabled,
...props
}: {
id: string;
disabled?: boolean;
}) {
return (
<Form replace method="delete" style={{ display: "inline" }}>
<input type="hidden" name="_method" value="delete" />
<input type="hidden" name="id" value={id} />{" "}
<button disabled={disabled} {...props}>
<TrashIcon />
</button>
</Form>
);
}
function TrashIcon() {
// https://heroicons.com/ trash
return (
<svg
style={{ width: "0.75rem", height: "0.75rem" }}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
);
}
function Delayed({
ms,
children,
}: {
ms: number;
children: React.ReactElement;
}) {
let [show, setShow] = React.useState(false);
React.useEffect(() => {
let id = setTimeout(() => {
setShow(true);
}, ms);
return () => clearTimeout(id);
}, []);
return show ? children : null;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment