Diagram
Inspired from this VDO.
How to over-engineer "todo" apps
Multi-Page Application (MPA) vs Single-Page Application (SPA)
script
Express JS
Pug
git clone https://github.com/fullstack-68/landscape-mpa.git mpa
cd mpa
pnpm install
pnpm run build
pnpm run start
./src/index.ts
app.get("/", async (req, res) => { const message = req.query?.message ?? ""; const todos = await getTodos(); // Output HTML res.render("pages/index", { todos, message, mode: "ADD", curTodo: { id: "", todoText: "" }, }); });
./view/pages/index.pug
body main(class="container") a(href="/") h1 Todo (MPA) div(id="todoform") include ../components/inputform.pug div(id="todolist") include ../components/todolist.pug
Use incognito mode to avoid loading chrome extensions.
input(type="hidden)
form(action="/delete" method="POST" style="display: contents") input(type="hidden" value=`${todo.id}` name="curId") button(type="submit" class="contrast" style="margin-bottom: 0") form(action="/edit" method="POST" style="display: contents") input(type="hidden" value=`${todo.id}` name="curId") button(type="submit" class="secondary" style="margin-bottom: 0")
React
git clone https://github.com/fullstack-68/landscape-spa.git spa
cd spa/backend
cd spa/frontend
pnpm run preview
zustand
JS
./src
./views
./backend/src
./frontend/src
Total: MPA=203, SPA=504
Back to the server. (Next JS)
SSR
CSR
V12
getServerSideProps
V13
useState
useEffect
Link
If RSC output is static, how can I mutate data then?
I had solved this problem before you were born, kid.
form-data
git clone https://github.com/fullstack-68/landscape-rsc-only.git rsc-only
cd rsc-only
./src/app/page.tsx
export default async function Home({ params, searchParams }: PageProps) { const todos = await getTodos(); //... return <main className="container">...</main>; }
console.log
./src/components/FormInput.tsx (Slightly modified)
./src/components/FormInput.tsx
export const FormInput: FC<Props> = async ({ message, mode, curId }) => { async function actionCreateTodo(formData: FormData) { "use server"; const todoText = formData.get("todoText") as string; // Receive form-data await createTodos(todoText); // DB stuff redirect("/?message=&curId=&mode=ADD"); // Update content without refreshing page (Nice!) } return ( <form action={actionCreateTodo} style={{ display: "contents" }}> <input type="hidden" name="curId" value={curId ?? ""} /> <button type="submit">{mode === "ADD" ? "Submit" : "Update"}</button> </form> ); };
No need to create endpoint manually.
./src/components/TodoList.tsx
todo
input
const ButtonDelete: FC<{ todo: Todo }> = ({ todo }) => { async function actionDeleteTodo(formData: FormData) { "use server"; await deleteTodo(todo.id); // revalidatePath("/"); } return ( <form action={actionDeleteTodo}> <button type="submit"></button> </form> ); };
(for my future self)
revalidatePath
redirect
UX
DX
searchParam
page.tsx
git clone https://github.com/fullstack-68/landscape-hybrid.git rsc-client
cd rsc-client
"use client"; // Mark component as client component //... export const TodoList: FC<Props> = ({ todos }) => { const { curTodo } = useStore((state) => state); return ( <> {...} </> ); };
export default function Home({ params, searchParams }: PageProps) { const todosPromise = getTodos(); return ( <main className="container"> <a href="/"> <h1>Todo (RSC + RCC)</h1> </a> <FormInput /> <Suspense fallback={<div>Loading...</div>}> <TodoList todosPromise={todosPromise} /> // Inject data to client component (SSR) </Suspense> <Spinner /> </main> ); }
./src/app/actionAdnDb.ts (Slightly modified)
./src/app/actionAdnDb.ts
"use server"; // Mark functions in this file as server actions. //... export async function actionCreateTodo(todoText: string) { //... await createTodos(todoText); //... revalidatePath("/"); return { message: "" }; }
return
const ButtonSubmit: FC<PropsButtonSubmit> = ({ setMessage }) => { // ... function handleClick() { startTransition(async () => { const res = await actionCreateTodo(inputText); setMessage(res.message); setInputText(""); }); } // ... return <button onClick={handleClick}>Submit</button>; };
form
(In Next JS project, I counted src dir.)
src
Have we gone too far?
Back to basic
Small JS library that can swap parts of UI with HTML response from a server.
2023 JavaScript Rising Stars
git clone https://github.com/fullstack-68/landscape-htmx.git htmx
cd htmx
express
pug
Take that NextJS!
Hypermedia as the Engine of Application State (HATEOAS)
HTMX + Alpine.js