Nesse desafio, você deverá criar uma aplicação para treinar o que aprendeu até agora no ReactJS
Essa será uma aplicação onde o seu principal objetivo é adicionar alguns trechos de código para que a aplicação de upload de imagens funcione corretamente. Você vai receber uma aplicação com muitas funcionalidades e estilizações já implementadas. Ela deve realizar requisições para sua própria API Next.js que vai retornar os dados do FaunaDB (banco de dados) e do ImgBB (serviço de hospedagem de imagens). A interface implementada deve seguir o layout do Figma. Você terá acesso a 4 arquivos para implementar:
A seguir veremos com mais detalhes o que e como precisa ser feito 🚀
Para realizar esse desafio, criamos para você esse modelo que você deve utilizar como um template do GitHub.
O template está disponível na seguinte URL:
rocketseat-education/ignite-template-reactjs-upload-de-imagens
Dica: Caso não saiba utilizar repositórios do GitHub como template, temos um guia em nosso FAQ.
Para esse desafio, iremos reforçar alguns pontos e apresentar algumas libs para te ajudar no desenvolvimento.
Começando pelo tema do projeto: upload de imagens. Como o desenvolvimento do zero acarretaria em um projeto muito grande, fornecemos no template a maior parte do projeto já implementada para que você tenha que trabalhar apenas com 4 arquivos. A ideia é que nesses 4 arquivos você tenha um pouco de contato com os 3 principais pontos que queremos abordar nesse projeto: React Query, React Hook Form e Chakra UI.
Dessa forma, antes de ir diretamente para o código do desafio, explicaremos brevemente como cada um dos pontos abaixo são importantes para o desafio:
Vamos nessa?
Na aplicação do desafio, você vai lidar com Infinite Queries, Mutations e Invalidações. Ao longo da seção O que devo editar na aplicação iremos mencionar quando cada uma dessas funcionalidades será utilizada, mas já vamos entender um pouquinho o papel de cada uma delas na nossa aplicação:
stale
e forçar a atualização dos dados. Ela será utilizada nessa aplicação para marcar a query de listagem de imagens como stale
quando a mutation de cadastrar uma nova imagem ocorrer com sucesso.Caso queira se aprofundar nesse assunto, deixaremos aqui alguns links que podem te ajudar
Na aplicação do desafio, você vai precisar implementar o registro dos inputs do formulário de cadastro da imagem, as validações e enviar os erros desses inputs.
Diferentemente do que foi visto na jornada, dessa vez você deve trabalhar com as validações diretamente no React Hook Form em vez de utilizar um resolver
do Yup.
Caso queira se aprofundar nesse assunto, deixaremos aqui um link que pode te ajudar:
Para o armazenamento das imagens do desafio, decidimos utilizar uma solução de hospedagem de arquivos gratuita e de fácil utilização chamada ImgBB. Não é a melhor solução para esse tipo de hospedagem, mas é a mais fácil pra vocês conseguirem implementar.
Portanto, para conseguir realizar os uploads das imagens para essa plataforma você precisar seguir 3 passos:
.env.local
da seguinte forma:NEXT_PUBLIC_IMGBB_API_KEY=VALOR_DA_CHAVE_COPIADA
Com esses três passos, você deve conseguir realizar o upload dessas imagens para o ImgBB normalmente. Caso tenha dúvidas, dê uma olhada no link abaixo:
Upload Image - Free Image Hosting
Para o armazenamento das informações das imagens (url, título e descrição), decidimos utilizar o FaunaDB já utilizado por você ao longo da jornada. Tudo que você precisa fazer é criar um banco no FaunaDB com um nome de sua preferência que precisa ter uma coleção chamada images
. Com o banco e coleção criados, basta você criar e copiar a chave do banco no seu arquivo .env.local
da seguinte forma:
FAUNA_API_KEY=VALOR_DA_CHAVE_COPIADA
Dessa forma você deve conseguir realizar o cadastro das informações das imagens no FaunaDB. Caso tenha dúvidas, reassista as aulas sobre a configuração do FaunaDB ou dê uma olhada no link abaixo:
Welcome to the Fauna documentation!
Nesse desafio toda a API do Next.js já foi implementada para você, porém vamos explicar rapidamente o que foi feito nessa etapa para que você entenda os dados que deve enviar e os dados que irá receber ao realizar as requisições.
query param
after
json
data: Dados formatados das imagens cadastradas no FaunaDB, exemplo:
"data": [
{
"title": "Doge",
"description": "The best doge",
"url": "<https://i.ibb.co/K6DZdXc/minh-pham-LTQMgx8t-Yq-M-unsplash.jpg>",
"ts": 1620222828340000,
"id": "294961059684418048"
},
]
after: Referência da próxima página de dados caso tenham mais imagens a serem carregadas do FaunaDB. Caso contrário, retorna null
.
body
que o cadastro irá ocorrer e, caso dê tudo certo, retornará uma mensagem success: true
.Como a maior parte do layout do figma já foi implementada, o seu foco nesse desafio deve ser implementar o grid da listagem de imagens e o Modal ao clicar na imagem desejada.
Para duplicar os layouts, basta você clicar no link abaixo. Ele adicionará o Layout à sua dashboard do Figma automaticamente, como uma cópia.
Com o template já clonado, as depêndencias instaladas e o Prismic já configurado, você deve completar onde não possui código com o código para atingir os objetivos de cada teste. Os documentos que devem ser editados são:
https://i.ibb.co/vDFrCcr/Home.png
Esse arquivo, por ser a única página do seu app, é o responsável direto ou indireto pela renderização de toda a sua aplicação.
A primeira coisa a se fazer é utilizar corretamente a Infinite Query. Portanto, no useInfiniteQuery
você precisa montar duas seções principais:
pageParam
(caso o parâmetro não exista, utilize como default
o valor null
). Esse parâmetro é utilizado no momento da requisição para chamarmos uma próxima página. Já no corpo da função, você deve realizar uma requisição GET para a rota /api/images
da API do Next.js informando como um query param
de nome after
o valor do pageParam
e retornar os dados recebidos.getNextPageParam
que recebe como parâmetro o resultado da última requisição. Se o valor after
retornado na última requisição existir, então você deve retornar esse valor, caso contrário retorne null
.<aside> 💡 Caso você esteja com dificuldades de entender como trabalhar com o useInfiniteQuery
, dê uma olhada nesse trecho da doc oficial
</aside>
Outro passo importante a se fazer é formatar os dados recebidos do React Query da forma que a aplicação espera. Portanto, no formattedData
é preciso que você utilize o map
e o flat
para que você transforme o data
(um objeto com mais informações do que o seu CardList.tsx
precisa) em um array de objetos apenas com as informações necessárias. Abaixo segue um exemplo de como fica antes e após a conversão.
{
"pages": [
{
"data": [
{
"title": "Doge",
"description": "The best doge",
"url": "<https://i.ibb.co/K6DZdXc/minh-pham-LTQMgx8t-Yq-M-unsplash.jpg>",
"ts": 1620222828340000,
"id": "294961059684418048"
},
{
"title": "Cachorrinho gif",
"description": "A Gracie é top",
"url": "<https://i.ibb.co/r3NbmgH/ezgif-3-54a30c130cef.gif>",
"ts": 1620222856980000,
"id": "295991055792210435"
},
{
"title": "React",
"description": "Dan Abramov",
"url": "<https://i.ibb.co/864qfG3/react.png>",
"ts": 1620223108460000,
"id": "295991069654385154"
},
{
"title": "Ignite",
"description": "Wallpaper Celular",
"url": "<https://i.ibb.co/DbfGQW5/1080x1920.png>",
"ts": 1620223119610000,
"id": "295991085899973123"
},
{
"title": "Ignite",
"description": "Wallpaper PC 4k",
"url": "<https://i.ibb.co/fvYLKFn/3840x2160.png>",
"ts": 1620223133800000,
"id": "295991107279389188"
},
{
"title": "Paisagem",
"description": "Sunset",
"url": "<https://i.ibb.co/st42sNz/petr-vysohlid-9fqw-Gq-GLUxc-unsplash.jpg>",
"ts": 1620223149390000,
"id": "295991128736399874"
}
],
"after": "295991160078336512"
}
],
"pageParams": [
null
]
}
[
{
"title": "Doge",
"description": "The best doge",
"url": "<https://i.ibb.co/K6DZdXc/minh-pham-LTQMgx8t-Yq-M-unsplash.jpg>",
"ts": 1620222828340000,
"id": "294961059684418048"
},
{
"title": "Cachorrinho gif",
"description": "A Gracie é top",
"url": "<https://i.ibb.co/r3NbmgH/ezgif-3-54a30c130cef.gif>",
"ts": 1620222856980000,
"id": "295991055792210435"
},
{
"title": "React",
"description": "Dan Abramov",
"url": "<https://i.ibb.co/864qfG3/react.png>",
"ts": 1620223108460000,
"id": "295991069654385154"
},
{
"title": "Ignite",
"description": "Wallpaper Celular",
"url": "<https://i.ibb.co/DbfGQW5/1080x1920.png>",
"ts": 1620223119610000,
"id": "295991085899973123"
},
{
"title": "Ignite",
"description": "Wallpaper PC 4k",
"url": "<https://i.ibb.co/fvYLKFn/3840x2160.png>",
"ts": 1620223133800000,
"id": "295991107279389188"
},
{
"title": "Paisagem",
"description": "Sunset",
"url": "<https://i.ibb.co/st42sNz/petr-vysohlid-9fqw-Gq-GLUxc-unsplash.jpg>",
"ts": 1620223149390000,
"id": "295991128736399874"
}
]
Em seguida, com a Infinite Query configurada e os dados formatados, você deve focar na renderização do seu app. Serão três renderizações diferentes:
isLoading
para ajudar a renderizar o componente Loading.tsx
no momento adequado.isError
para ajudar a renderizar o componente Error.tsx
no momento adequado.return
que já deixamos parcialmente montado para você e que explicaremos com mais detalhes abaixo o que falta ser implementado.No caso dos dados terem sido carregados com sucesso, você deve renderizar o Header da aplicação e as imagens cadastradas no FaunaDB, mas não todas de uma vez.
Serão exibidos 6 cards por vez, caso tenha mais imagens para serem carregadas, um botão escrito Carregar mais
deve ser exibido. Caso contrário, o botão não deve ser renderizado. Além disso, ao clicar no botão Carregar mais
é preciso que ele altere a mensagem do texto para Carregando...
enquanto o React Query realiza a busca dos novos dados. Para que essas funcionalidades do botão deem certo, utilize o hasNextPage
, isFetchingNextPage
e fetchNextPage
do React Query.
<aside> 💡 Caso você esteja com dificuldades de entender como trabalhar com o hasNextPage
, isFetchingNextPage
e fetchNextPage
, dê uma olhada nesse trecho da doc oficial
</aside>
https://i.ibb.co/vDFrCcr/Home.png
Esse arquivo é responsável pela exibição do grid de cards e o controle do Modal de exibição da imagem selecionada.
Os cards devem ser renderizados em um grid de 3 colunas com um espaçamento de 40px. Utilize o componente Card.tsx
para a renderização dos cards.
Ao clicar no card, é preciso abrir a modal ViewImage.tsx
. Utilize a prop viewImage
do Card.tsx
para disparar uma função, que recebe como argumento a url da imagem, e irá lidar com a abertura do Modal ViewImage.tsx
e o envio da url da imagem desejada.
https://i.ibb.co/615PvJd/Form.png
Nesse modal serão exibidos uma imagem e o link.
Essa imagem deve ter como largura máxima 900px
e como altura máxima 600px
, mantendo a proporção da imagem dependendo de qual dessas duas medidas chegar no valor máximo primeiro.
Por exemplo, um wallpaper de celular tem a altura muito maior que a largura, já um wallpaper de um monitor widescreen tem a largura muito maior que a altura. Portanto, um exemplo prático desse dois casos seria os prints abaixo:
Em relação ao link, ele deve ser renderizado abaixo da imagem com o texto Abrir original
que aponta para o endereço onde a imagem está hospedada no ImgBB. Ao clicar no link, ele deve abrir uma nova aba no navegador
Nesse arquivo, você tem quatro etapas principais que precisa implementar:
mutation
do React Query;onSubmit
;Começando pelas validações, você encontrará um objeto formValidations
com as propriedades image
, title
e description
. Essas propriedades representam cada um dos três inputs do formulário e é dentro delas que você irá declarar as validações desses inputs. As validações são:
required: Campo obrigatório, não pode estar vazio no momento do envio. A mensagem de erro deve ser Arquivo obrigatório
validate: Como as outras duas validações são customizadas, ou seja, não tem por padrão no React Hook Form, iremos criá-las manualmente com o validate
.
<aside> 💡 Caso tenha dúvidas de como trabalhar com essa propriedade, dê uma olhada nesse trecho da doc oficial
</aside>
size
do arquivo de imagem para realizar a comparação. A mensagem de erro deve ser O arquivo deve ser menor que 10MB
.type
do arquivo de imagem e um regex
com os tipos aceitos para realizar a comparação. A mensagem de erro deve ser Somente são aceitos arquivos PNG, JPEG e GIF
.Título obrigatório
Mínimo de 2 caracteres
.Máximo de 20 caracteres
.Descrição obrigatória
Máximo de 65 caracteres
.Agora que você possui as validações criadas, é hora de registrar os seus inputs no React Hook Form. Portanto, em cada um dos seus três inputs, é preciso que você envie
register
que possui o nome do seu input como o primeiro parâmetro e a validação desse input como segundo parâmetro.error
na qual você deve mandar o erro referente ao seu input. Utilize o errors
obtido na desestruturação do formState
.Outro etapa que precisa ser implementada nesse arquivo é a mutation
do React Query. Essa mutation
será responsável pelo cadastro da nova imagem no FaunaDB. Portanto, como primeiro argumento do useMutation
, você deve implementar uma função que recebe como parâmetro os dados do formulário e no seu corpo realizar uma requisição POST para a rota api/images
enviando os dados recebidos. Já como segundo parâmetro, você irá utilizar a propriedade onSuccess
da mutation
para que, caso ela ocorra com sucesso, invalidade a query
que listou as imagens, forçando o React Query a atualizar esses dados. Para isso, trabalhe com o método invalidateQueries
.
<aside> 💡 Caso tenha dúvidas de como trabalhar com essa propriedade, dê uma olhada nesse trecho e também nesse trecho da doc oficial
</aside>
Por fim, é preciso implementar a função onSubmit
. Essa função recebe como argumento os dados do formulário de cadastro da imagem. No corpo da função, você encontrará três seções:
imageUrl
existe. Se não existir, mostrar um toast
de informação com o título Imagem não adicionada
e descrição É preciso adicionar e aguardar o upload de uma imagem antes de realizar o cadastro.
e sair imediatamente da função. Caso exista, basta seguir para as próximas etapas.mutation
utilizando o mutateAsync
para pode aguardar o resultado da Promisse.toast
de sucesso com o título Imagem cadastrada
e descrição Sua imagem foi cadastrada com sucesso.
toast
de erro com o título Falha no cadastro
e descrição Ocorreu um erro ao tentar cadastrar a sua imagem.
Em cada teste, você encontrará uma breve descrição do que sua aplicação deve cumprir para que o teste passe.
<aside> 💡 Caso você tenha dúvidas quanto ao que são os testes, e como interpretá-los, dê uma olhada em nosso FAQ
</aside>
Para esse desafio, temos os seguintes testes: