DEV Community

Junior Martins
Junior Martins

Posted on

Remix :: CRUD com Supabase - Parte 05 - Trabalhando com formulário

Nesta parte veremos o quanto é fácil trabalhar com formulários no Remix, iremos melhorar nossa estrutura de arquivos, colocando as coisas nos lugares certos para evitar código duplicado e permitir assim o reaproveitamento na nossa aplicação.

🎲 Criando o modelo

Nas partes anteriores deste tutorial nós criamos a PostModel nos arquivos onde ele era necessário, mas agora iremos deixá-lo em lugar onde "quem precisar" na aplicação possa usá-lo, portanto, crie o arquivo posts.model.ts dentro da pasta app/models/, de forma que ele fique assim: app/models/posts.model.ts. Edite-o e deixe desta forma:

export type PostsModel = {
  post_id?: number
  post_uuid?: string
  post_author: string
  post_title: string
  post_text: string
  post_situation?: string
  post_created_at?: string
}
Enter fullscreen mode Exit fullscreen mode

Criando a API

Criado o modelo, podemos agora trabalhar com a API, que armazenará todas as ações que permitirão executar nosso CRUD, para isto, crie o arquivo supabase-api.ts dentro do diretório app/api/, de forma que fique assim: app/api/supabase-api.ts.

  1. Edite o arquivo supabase-api.ts e import o PostsModels e o cliente supabase, deixando-o assim:
import { PostsModel } from '~/models/posts.model'
import { supabase } from '~/utils/supabase-client.server'
Enter fullscreen mode Exit fullscreen mode
  1. Ainda no supabase-api.ts. crie a função getPosts, que ficará encarregada de realizar um select no Supabase para nós.
const getPosts = async () => {
  const { data: posts } = await supabase
    .from<PostsModel>('posts')
    .select('*')
    .order('post_id', { ascending: false })

  return posts
}
Enter fullscreen mode Exit fullscreen mode
  1. Agora criaremos a função addPost, que ficará responsável por adicionar um registro novo.
const addPost = async ({ post_author, post_title, post_text }: PostsModel) => {
  const { data, error } = await supabase.from<PostsModel>('posts').insert([
    {
      post_author: post_author,
      post_title: post_title,
      post_text: post_text,
    },
  ])

  return { data, error }
}
Enter fullscreen mode Exit fullscreen mode
  1. Por fim, exporte as duas funcões:
export { getPosts, addPost }
Enter fullscreen mode Exit fullscreen mode

Criando a rota posts

Crie dentro da pasta routes um arquivo com nome posts.tsx e dentro dele faça os seguintes imports:

import { Link, LoaderFunction, Outlet, useLoaderData } from 'remix'
import { getPosts } from '~/api/supabase-api'
import { PostsModel } from '~/models/posts.model'
Enter fullscreen mode Exit fullscreen mode
  • Link para criarmos o link que irá chamará o formulário para iserir um novo registro;
  • LoaderFunction para tiparmos o método loader que ficará responsável por chamar a função getPosts, que carrega os registros do banco de dados;
  • useLoaderData permite façamos o uso dos dados carregados pelo loader;
  • Outlet, este componente é um wrapper em torno do Outlet do React Router com a capacidade de passar o estado da interface do usuário para rotas aninhadas. Ou seja, com ele será possível carregar a rota filha que conterá o fomulário dentro da rota pai posts.tsx;
  • getPosts é a função que carrega os registros do banco de dados; e
  • postModels é o type model dos dados que virão do banco de dados.

Feito as importações criaremos o método loader para chamar a getPosts:

export const loader: LoaderFunction = async () => {
  const posts = await getPosts()

  return posts
}
Enter fullscreen mode Exit fullscreen mode

Agora iremos criar nosso componente Posts:

export default function Posts() {
  const posts = useLoaderData<PostsModel[]>()

  return (
    <div style={{ fontFamily: 'system-ui, sans-serif', lineHeight: '1.4' }}>
      <h1>Blog Remix com Supabase</h1>
      <ul
        style={{
          listStyle: 'none',
          display: 'flex',
          flexDirection: 'row',
          gap: '8px',
        }}
      >
        <li>
          <a
            href='/'
            style={{
              textDecoration: 'none',
              backgroundColor: '#ef62df',
              color: '#fff',
              padding: '8px',
              borderRadius: '4px',
              fontSize: '14px',
            }}
          >
            Home
          </a>
        </li>
        <li>
          <Link
            to={'new'}
            style={{
              textDecoration: 'none',
              backgroundColor: '#62efd0',
              color: '#0d6443',
              padding: '8px',
              borderRadius: '4px',
              fontSize: '14px',
            }}
          >
            Novo
          </Link>
        </li>
      </ul>
      <div>
        <Outlet />
      </div>
      <div>
        <ul style={{ listStyle: 'none' }}>
          {posts?.map(post => (
            <li key={post.post_uuid}>
              <h3>{post.post_title}</h3>
              <small>{post.post_author}</small>
              <blockquote>{post.post_text}</blockquote>
            </li>
          ))}
        </ul>
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

E, por fim, chamaremos a função ErrorBoundary do Remix, a qual permite que os erros sejam capturados dentro da rota que foi chamada, evitando assim que toda a aplicaçãose quebre:

export function ErrorBoundary({ error }: { error: Error }) {
  return (
    <div className='error-container'>
      <h1>😱 App Error</h1>
      <pre> {error.message}</pre>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Criando a página do fomulário

Criado a página de listagem dos posts, iremos criar a página que conterá o fomulário que permitirá que a gente insira novos registros, para isto crie a pasta posts dentro de routes, de forma que fique routes/posts. Dentro da pasta posts crie o arquivo new.tsx e faça as seguintes imports:

import { ActionFunction, Link, redirect } from 'remix'
import { supabase } from '~/utils/supabase-client.server'
Enter fullscreen mode Exit fullscreen mode
  • ActionFunction para tiparmos o método do Remix chamado action que é reponsável por "capturar" as variáveis enviadas pelo fomulário. Na verdade action pega todas varáveis de qualquer request na rota onde ele está.
  • redirect permite que a rota seja redirecionada
  • supabase é o nosso cliente do Supabase

Feito os imports, chamaremos o método action:

export const action: ActionFunction = async ({ request }) => {
  const inputs = Object.fromEntries(await request.formData())

  await supabase.from('posts').insert([inputs])

  return redirect('.')
}
Enter fullscreen mode Exit fullscreen mode

Agora criaremos o componente NewPost que conterá nosso formulário:

export default function NewPost() {
  return (
    <div style={{ fontFamily: 'system-ui, sans-serif', lineHeight: '1.4' }}>
      <h1>Novo post</h1>
      <form
        action='#'
        method='post'
        style={{
          listStyle: 'none',
          display: 'flex',
          flexDirection: 'column',
          gap: '8px',
        }}
      >
        <div>
          <label htmlFor=''>Título</label>
          <br />
          <input type='text' name='post_title' />
        </div>
        <div>
          <label htmlFor=''>Autor</label>
          <br />
          <input type='text' name='post_author' />
        </div>
        <div>
          <label htmlFor=''>Texto</label>
          <br />
          <textarea
            name='post_text'
            id='post_text'
            cols={30}
            rows={10}
          ></textarea>
        </div>
        <div>
          <button
            type='submit'
            style={{
              backgroundColor: '#62a4ef',
              color: '#fff',
              padding: '8px',
              borderRadius: '4px',
              border: 'none',
              lineHeight: '1.4',
              fontSize: '14px',
              cursor: 'pointer',
            }}
          >
            Enviar
          </button>
          <Link
            to={'/posts'}
            style={{
              marginLeft: '10px',
              textDecoration: 'none',
              backgroundColor: '#ef62df',
              color: '#fff',
              padding: '8px',
              borderRadius: '4px',
              fontSize: '14px',
            }}
          >
            Cancelar
          </Link>
          <br />
        </div>
      </form>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Com isto finalizamos o formulário e na parte 06 iremos criar nosso primeiro post através do formulário criado. Te espero lá! 😉

Top comments (0)