Lecciones del curso

Aprende Remix construyendo un Blog con MongoDB y Netlify

Clonando el proyecto
2m
Anatomía del proyecto
4m
Agregando TailwindCSS
3m
Creando y conectando con la base de datos
8m
Ruta para el login
Maquetando el dashboard
Editando posts | Primera parte
Editando posts | Segunda parte
Preparando el UI público
Redireccionando en la Página de inicio
Vista de lista
CSS para el Markdown
Blog listo para producción con Netlify
Creando Feedlinks (SSR)
Creando un sitemap.xml
Felicitación y despedida

Editando posts | Primera parte

Esta es la sección más importante de toda la aplicación. Aquí es donde pasará la mayor pate del tiempo nuestro autor, ya seas tú o tus usuarios.

Así que vamos a ponerle esmero a esta sección.

Puede que sea la lección más fructífera para ti, de todo el curso*.* 🏄🏻 🤓

Creamos una ruta dinámica

Agregamos el archivo: app/routes/dash_.$postId.edit, aquí es donde vamos a usar nuestro editor.

👀 En Remix podemos definir una ruta que sea parte del segmento, pero no necesariamente de su layout, con _ (dash_.). /dash/<postId>/edit = dash_.$postId.edit, esto es practico pues no tenemos que pensar en <Outlet />

Maquetando el formulario

Como primer paso, colocaremos inputs simples para la configuración básica de nuestro post.

// app/routes/dash_.$postId.edit.tsx export default function PostEdit() { const { title, content, body, cover, tags, published } = useLoaderData<typeof loader>(); const fetcher = useFetcher(); return ( <> <fetcher.Form method="post" > <div className=""> <label className="text-xs mb-2"> Escribe un título para tu post: </label> <input className="py-2 px-4 rounded-md w-full text-gray-800" type="text" placeholder="Escribe un título" defaultValue={title} name="title" /> </div> </fetcher.Form> </> ); }

Este formulario recogerá los datos básicos de nuestro post. ✅

👀 Observa que en el nombre del archivo, hemos colocado un _ justo al terminar la palabra dash. Esto lo hacemos para tener una ruta NO anidada, es decir independiente de la ruta /dash, pero que comparte el nombre del segmento en la barra de direcciones. Más sobre esto en la documentación oficial de Remix.

Agregando el editor

Vamos a usar una herramienta open source que nos permite tener un editor simplificado, con Mardown, que también incluye un visualizador. 🔭

Esta biblioteca se llama: @uiw/react-codemirror. Vamos a instalarla.

npm i @uiw/react-codemirror

Creamos un componente aparte, llamado MarkdownEditor.tsx

// app/components/MarkdownEditor.tsx import { signal } from "@preact/signals-react"; import MDEditor from "@uiw/react-markdown-editor"; export default function MarkdownEditor({ onChange, defaultValue = "# Esto es un H1 \n## Esto es un H2 \n###### Y esto es un H6", }: { onChange?: (value: string) => void; defaultValue?: string; }) { const state = signal(defaultValue); const handleChange = (value: string) => { state.value = value; if (onChange) onChange(value); }; return ( <MDEditor style={{ minHeight: "50vh", }} value={state.value} onChange={handleChange} /> ); }

Puedes observar con detenimiento este componente leyéndolo en el repositorio, pero me resulta importante mencionarte que este es el componente más simple que encontré allá afuera, en la jungla de npm, si sabes de uno mejor, dímelo porfa.

🐒 En algunos otros proyectos también he utilizado StackEdit al que te recomiendo mucho le eches un ojo. 👀

Agregando los campos para SEO

Vamos a simplificar lo más posible.

Siempre puedes agregar la complejidad que tus propios fines requiera, pero para alcanzar nuestro objetivo más básico, por ahora, para nuestro post, solo vamos a necesitar:

  1. Un link de la imagen. Que se ocupara tanto de la portada como de la meta-imagen.
  2. Y un tag. para poder clasificar nuestros posts*.

El Maquetado va así:

// ... return ( <> <main className="max-w-3xl mx-auto py-20"> <fetcher.Form> <div className=""> <label className="text-xs mb-2">Editando tu post:</label> <input className="py-2 px-4 rounded-md w-full" type="text" placeholder="Escribe un título" defaultValue={title} name="title" /> </div> <section className="flex gap-4 items-end"> <div className=""> <label className="text-xs">Agrega una imagen de portada:</label> <input className="py-2 px-4 rounded-md w-full" type="text" placeholder="Pega el link de una imagen" // defaultValue={cover} name="cover" /> </div> <div className=""> <label className="text-xs ">Agrega un tag:</label> <input className="py-2 px-4 rounded-md w-full" type="text" placeholder="Escribe un título" // defaultValue={tag} name="title" /> </div> <button className="px-6 bg-gradient-to-bl from-indigo-500 to-blue-500 text-white rounded-md hover:to-b.ue-700 py-2"> Guardar </button> </section> </fetcher.Form> <hr className="border-none bg-indigo-500 h-[1px] my-2" /> <MarkdownEditor defaultValue={body} /> </main> </> );

Observa como debajo del formulario tenemos el editor de Markdown.

El JSX está listo, es momento de trabajar en el business logic del lado del servidor.

Escribiendo el loader

Del lado del servidor necesitamos que sucedan dos cosas, una en el loader al iniciar y otra en el action, para actualizar nuestro modelo.

import type { LoaderFunction, LinksFunction } from "@remix-run/cloudflare"; import editorStyles from "@uiw/react-markdown-editor/markdown-editor.css"; import MarkdownEditor from "~/components/MarkDownEditor"; import { useLoaderData } from "@remix-run/react"; // Inyectamos los estilos del Markdown export const links: LinksFunction = () => { return [ { rel: "stylesheet", href: editorStyles, }, ]; }; export const loader = async ({ params }:LoaderArgs) => { // Conseguimos el post a editar const post = await db.post.findUnique({ where: { id: params.postId }, select: { title: true, id: true, body: true, tags: true, }, }); if (!post) { // Si no hay coincidencias con el id devolvemos un 404 return json({ ok: false, error: "not found" }, { status: 404 }); } return post; }; export default function PostEdit() { const { title, body } = useLoaderData(); const fetcher = useFetcher(); // ...

Observa cómo el loader ha conseguido el post que se va a editar, todas las llaves del post son deconstruidas desde el Hook useLoaderData. Así podemos colocarlos en el input correspondiente en la propiedad [defaultValue](http://defaultValue.de) de cada input.

<input className="py-2 px-4 rounded-md w-full text-gray-800" type="text" placeholder="Escribe un título" defaultValue={title} name="title" />

Así estaríamos trabajando con un formulario no-controlado. 🤯

Muy bien. ¡Gran avance! 🫱🏼‍🫲🏾

En la segunda parte, escribiremos el action, modificaremos el modelo Post en el esquema de Prisma e inmediatamente agregaremos el guardado automático del post en la base de datos. 🤯

También nos ocuparemos de agregar una validación simple, pero robusta. Empleando Zod.

¡No perdamos tiempo y continuemos con la segunda parte! 🚀 🪐

Enlaces relacionados

StackEdit:

https://stackedit.io/app#

spaceman

¿List@ para ver todo el curso? Prepárate porque apenas estamos comenzando 🚀

¡Desbloquea el curso completo y conviértete en un PRO del desarrollo web! 🫶🏻 .