“Só deseje as coisas as quais você está disposto a lutar”!
Sobre o desafio | Entrega | Licença
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:
- Infinite Queries e Mutations com React Query;
- Envio de formulário com React Hook Form;
- Exibição de Modal e Toast com Chakra UI;
- Entre outros.
A seguir veremos com mais detalhes o que e como precisa ser feito 🚀
Para te ajudar nesse desafio, criamos para você um modelo que você deve utilizar como um template do Github.
O template está disponível na seguinte url: Acessar Template
Dica: Caso não saiba utilizar repositórios do Github como template, temos um guia em nosso FAQ.
Agora navegue até a pasta criada e abra no Visual Studio Code, lembre-se de executar o comando yarn
no seu terminal para instalar todas as dependências.
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:
- React Query;
- React Hook Form;
- ImgBB;
- FaunaDB;
- API do Next.js;
- Figma.
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:
- Infinite Queries: Listagem que adiciona mais dados ao clicar em um botão de carregamento ou "infinite scroll". Ela será utilizada nessa aplicação para realizar o carregamento das imagens cadastradas no nosso banco. O carregamento foi implementado com um clique em um botão, não o "infinite scroll" (já fica aí a sugestão de um extra para o desafio).
- Mutations: Diferente das queries do React Query que são utilizadas normalmente para a busca de dados, as mutations são responsáveis pela criação/edição/remoção de dados. Ela será utilizada nessa aplicação para o cadastro de uma nova imagem no banco.
- Invalidações: Utilizada para marcar manualmente uma query como
stale
e forçar a atualização dos dados. Ela será utilizada nessa aplicação para marcar a query de listagem de imagens comostale
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:
- Criar uma conta no ImgBB;
- Criar a sua chave da API;
- Copiar o valor dessa chave e colar no seu
.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.
- GET api/images: Essa é a rota utilizada para listagem das imagens. Ela rota recebe um
query param
de nomeafter
que indica caso haja mais dados a serem carregados do FaunaDB. Por padrão, foi definido que a paginação da resposta do FaunaDB é de 6 dados. A resposta da API é umjson
com dois valores:-
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
.
-
- POST api/images: Essa é a rota utilizada para cadastro das informações da imagem (url, título e descrição) no FaunaDB. Tudo que você precisa enviar são esses três dados pelo
body
que o cadastro irá ocorrer e, caso dê tudo certo, retornará uma mensagemsuccess: 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:
- src/pages/index.tsx;
- src/components/Form/FormAddImage.tsx;
- src/components/Modal/ViewImage.tsx;
- src/components/CardList.tsx.
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:
- Uma função que recebe como parâmetro um objeto que contêm a propriedade
pageParam
(caso o parâmetro não exista, utilize comodefault
o valornull
). 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 umquery param
de nomeafter
o valor dopageParam
e retornar os dados recebidos. - Uma função chamada
getNextPageParam
que recebe como parâmetro o resultado da última requisição. Se o valorafter
retornado na última requisição existir, então você deve retornar esse valor, caso contrário retornenull
.
Caso você esteja com dificuldades de entender como trabalhar com o useInfiniteQuery
, dê uma olhada nesse trecho da doc oficial
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:
- Quando o React Query estiver carregando os dados: Nesse caso você deve utilizar o
isLoading
para ajudar a renderizar o componenteLoading.tsx
no momento adequado. - Quando o React Query tiver falhado ao carregar os dados: Nesse caso você deve utilizar o
isError
para ajudar a renderizar o componenteError.tsx
no momento adequado. - Quando o React Query tiver carregado os dados com sucesso: Nesse caso, quando o app não cair em uma das duas renderizações anteriores, será considerado a renderização de sucesso. No caso desse app, é o
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.
Caso você esteja com dificuldades de entender como trabalhar com o hasNextPage
, isFetchingNextPage
e fetchNextPage
, dê uma olhada nesse trecho da doc oficial
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.
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:
Wallpaper celular (limitou pela altura max de 600px)
Wallpaper pc (limitou pela largura max de 900px)
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:
- As validações do formulário;
- A
mutation
do React Query; - A função
onSubmit
; - O registro dos inputs no React Hook Form.
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:
- image:
-
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
.Caso tenha dúvidas de como trabalhar com essa propriedade, dê uma olhada nesse trecho da doc oficial
- lessThan10MB: O tamanho do arquivo não pode exceder 10 MB. Utilize a propriedade
size
do arquivo de imagem para realizar a comparação. A mensagem de erro deve serO arquivo deve ser menor que 10MB
. - acceptedFormats: O arquivo enviado pelo usuário deve ser um dos três tipos: image/jpeg, image/png ou image/gif. Utilize a propriedade
type
do arquivo de imagem e umregex
com os tipos aceitos para realizar a comparação. A mensagem de erro deve serSomente são aceitos arquivos PNG, JPEG e GIF
.
- lessThan10MB: O tamanho do arquivo não pode exceder 10 MB. Utilize a propriedade
-
- title:
- required: Campo obrigatório, não pode estar vazio no momento do envio. A mensagem de erro deve ser
Título obrigatório
- minLength: O tamanho mínimo da string deve ser de 2 caracteres. A mensagem de erro deve ser
Mínimo de 2 caracteres
. - maxLength: O tamanho máximo da string deve ser de 20 caracteres. A mensagem de erro deve ser
Máximo de 20 caracteres
.
- required: Campo obrigatório, não pode estar vazio no momento do envio. A mensagem de erro deve ser
- description:
- required: Campo obrigatório, não pode estar vazio no momento do envio. A mensagem de erro deve ser
Descrição obrigatória
- maxLength: O tamanho máximo da string deve ser de 65 caracteres. A mensagem de erro deve ser
Máximo de 65 caracteres
.
- required: Campo obrigatório, não pode estar vazio no momento do envio. A mensagem de erro deve ser
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
- Uma propriedade
register
que possui o nome do seu input como o primeiro parâmetro e a validação desse input como segundo parâmetro. - Uma propriedade
error
na qual você deve mandar o erro referente ao seu input. Utilize oerrors
obtido na desestruturação doformState
.
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
.
Caso tenha dúvidas de como trabalhar com essa propriedade, dê uma olhada nesse trecho e também nesse trecho da doc oficial
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:
- try: Nesse trecho você deve se concentrar em três etapas:
- Verificar se o
imageUrl
existe. Se não existir, mostrar umtoast
de informação com o títuloImagem 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. - Executar a
mutation
utilizando omutateAsync
para pode aguardar o resultado da Promisse. - Mostrar um
toast
de sucesso com o títuloImagem cadastrada
e descriçãoSua imagem foi cadastrada com sucesso.
- Verificar se o
- catch: Nesse trecho você deve mostrar um
toast
de erro com o títuloFalha no cadastro
e descriçãoOcorreu um erro ao tentar cadastrar a sua imagem.
- finally: Nesse trecho você deve limpar os campos do form, os estados do componente e fechar o modal.
Em cada teste, você encontrará uma breve descrição do que sua aplicação deve cumprir para que o teste passe.
Caso você tenha dúvidas quanto ao que são os testes, e como interpretá-los, dê uma olhada em nosso FAQ
Para esse desafio, temos os seguintes testes:
Esse desafio deve ser entregue a partir da plataforma da Rocketseat. Envie o link do repositório que você fez suas alterações. Após concluir o desafio, além de ter mandado o código para o GitHub, fazer um post no LinkedIn é uma boa forma de demonstrar seus conhecimentos e esforços para evoluir na sua carreira para oportunidades futuras.
Esse projeto está sob a licença MIT. Veja o arquivo LICENSE para mais detalhes.
Feito com 💜 by Rocketseat 👋 Entre na nossa comunidade!