DEV Community

Cover image for FrontEndMentor.io Challenge #1
Guilherme Leme
Guilherme Leme

Posted on • Edited on

FrontEndMentor.io Challenge #1

Como parte da minha rotina de estudos, no início dessa semana comecei um novo projeto do FrontEndMentor.io. O desafio escolhido da vez foi o IP Address Tracker, uma aplicação simples onde ao acessá-la o usuário deve poder ver seu próprio endereço IP público, entre outras especificações que serão listadas abaixo. 

Briefing 

O IP Address Tracker, como o nome já diz, deve poder rastrear o endereço IP público do usuário e retorná-lo com algumas outras infos, como cidade, estado, timezone e provedor de internet. Um pin deve ser marcado no mapa, utilizando a [lat,lng] retornado pelo Ipify. 

O desafio possui um layout pré-definido, com uma versão para desktop otimizada para 1440px, e outra para mobile para 375px. Além disso, foi disponibilizado um style-guide, com a fonte com as variações a serem usadas e a paleta de cores (em hsl).

Apesar do dev ser livre para usar quaisquer libs, frameworks e ferramentas que julgue necessário ou que simplesmente queira praticar, o desafio sugere que sejam usadas a IP Geolocation API da Ipify, para pesquisar os endereços IP, e o LeafletJS para gerar o mapa. 

Tools

Como estou desenvolvendo minhas habilidades em React, o escolhi. Junto ao React, escolhi trabalhar com o react-leaflet para exibir o mapa, styled-components para estilização, Unform e Yup para o "formulário" e sua validação.

O projeto

Para esse desafio optei por usar CRA com --template typescript.

Sou iniciante e os primeiros contatos que tive com desenvolvimento foram para web, lidando com telas maiores, porém conforme fui me aprofundando nos meus estudos tive contato com o conceito de mobile first, então resolvi aplicá-lo aqui. Assim, quando o CRA terminou de criar o projeto, já fui logo deletando aqueles arquivos que não iria usar, deixando apenas o mínimo necessário para mostrar em tela o tão querido <h1>HELLO WORLD</h1>.

Com aquele h1 todo feioso em tela, comecei resetando os estilos, e fazendo as primeiras configurações CSS, já adicionando a fonte e cores indicadas no projeto. 

Assim, iniciei meu primeiro componente que, na falta de um nome melhor, chamei de Map 😛. Neste componente, criei uma <div className='background'> , composta por uma imagem (disponibilizada pelo FrontEndMentor), e pelo mapa, disposto um logo abaixo do outro com display:flex e flex-direction: column. Como optei por tratar esse componente como um background também apliquei z-index: -2 nele, para que quando o chamasse, ele já fosse para baixo dos outros componentes. Ainda precisei usar position: absolute para organizá-lo melhor na tela.

Com o mapa já em tela (usei o example code disponibilizado no site do React-Leaflet para renderizá-lo), parecia que o pior já havia passado 🤓. Então comecei a criar os próximos componentes, que foram o searchbar, que chamei apenas de <Input />(na época não me toquei que ele não era apenas um simples input haha), e um <Card /> onde seria apresentado todas as infos já descritas anteriormente. 

Criei também um componente chamado <Layout /> onde basicamente organizei todos os outros componentes para chamá-los de uma só vez no App.tsx. Achei que ficou legal dessa forma. Então com uso de media query e mudando alguns flex-direction adaptei o layout para sua versão desktop e ficou bonitão. 

Até aqui foi de boa, mesmo de forma estática já tinha tudo o que precisava para começar a "dar vida" aquele corpo inerte de <div> , <h1> e tantas outras tags. 

Para me conectar a API da Ipify usei o Axios, uma solução um pouco mais bonita e performática do que o fetch() do próprio javascript. Precisei também de uma API_KEY que foi bem rápido de ser criada, e já tratei de salvá-la no meu .env

Após isso, tentei fazer a chamada API dentro do componente <Layout /> e já passar os dados para os devidos componentes por meio das props. Não tão surpreendentemente assim, tudo funcionou bem, só precisei fazer alguns ajustes CSS, e na minha tela já estava aparecendo meu endereço IP, minha localização, timezone e provedor de internet 🎉. 

"Haha! Sou melhor do que imaginei, me aguarde mercado de trabalho 😎", pensei enquanto analisava o que havia conseguido fazer em apenas um dia. Bem, com o coração quentinho por tudo ter dado certo no primeiro dia, fui descansar, mas antes resolvi anotar o que eu queria terminar no dia seguinte. 

"Preciso mudar o mapIcon para o correto, fazer funcionar a barra de pesquisa, e o mapa precisa mostrar a localização da pesquisa do usuário. Simples!" 

No outro dia, ao iniciar a aplicação um erro apareceu na tela, alguma coisa como "Cannot read property of undefined" blábláblá🙄.

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH!

Tudo bem, um erro simples, vai ser fácil de corrigir. Identifiquei que a chamada API da Ipify demorava um pouco para responder e salvar o response.data no state e eu já estava criando os componentes com esses dados. Coloquei algumas condições para que enquanto a chamada não finalizasse exibisse "Loading…" nos componentes e voilà, voltou a funcionar. 

S U A V Ã O! Talvez não tenha sido a melhor solução, mas definitivamente estava funcionando. 

Hora de focar no que eu tinha que resolver neste dia. Comecei fazendo a searchbar ficar funcional. Com algumas linhas de código já estava recebendo os dados requisitados e apresentando-os no console do Chrome. 

Mas tinha uma coisa que eu não havia me atentado até então: para cada tipo de pesquisa eu usaria um endpoint diferente. Para pesquisar por um endereço IP eu deveria usar &ipAdress= e para pesquisar por um domínio usaria &domain=.
 
Resolvi isso de forma muito classuda. Há uns dias atrás estava fazendo um módulo do curso de javascript da FreeCodeCamp, justamente sobre expressões regulares, as RegEx. Notei que esse desafio seria uma ótima oportunidade para utilizá-las e treinar minhas habilidades, fiquei felizão.

Desta forma, criei uma variável que verifica se a pesquisa é ou não um endereço IP (na verdade só verifica se a pesquisa começa com um número ou não, se começar assumi que os próximos caracteres também seriam números, logo seria um endereço IP). Assim surgiu a tal forma classuda dita lá em cima, const isSearchAnIPAddress = !!data.search.match(/^\d/).

Usei isSearchAnIPAddress em um if de forma muito legível. if(isSearchAnIPAddress) usa o endpoint de pesquisa por endereço IP, senão, usa o endpoint para domínios. 

Fiz os testes novamente e G E E E E E E Z Z! To voando nesse desafio. 

And I found a dream worth fighting for
And nothing can stop me
Nothing can stop me
Nothing can stop me
Nothing can stop me nooooow
(Nothing can stop me now - Filme ruim da Disney que ninguém assistiu)

Porém, ainda tinham coisas a serem feitas, como, por exemplo, mudar o mapIcon, o que foi até que fácil. Lembra que eu falei que tinha participado de um NLW da Rocketseat, pois é, foi só copiar o código que já estava pronto lá. Aproveitei e acessei a plataforma para assistir o Diego Fernandes explicando como isso funcionava, porém os vídeos estavam fora do ar (a galera do twitter da Rocketseat me informou que houve um probleminha com o Vimeo, mas alguns dias após entrei novamente e já estavam todos lá).

Também ainda tinha que tratar o valor do input para ser usado na requisição de forma correta. Incialmente pensei em criar uma máscara para o input que seria usada apenas se o primeiro caractere digitado fosse um número. Essa máscara iria adicionar um ponto a cada três números digitados, e seria basicamente isso mesmo (tentaria fazer isso com RegEx). 

Ao pesquisar mais sobre os endereços IP, descobri que eles são formados basicamente por 4 "grupos" de números separados por pontos, e que esses números podem variar de 0 a 255. Isso abre margem para o usuário pesquisar por um IP no formato x.x.x.x, xxx.xxx.xx.xxx ou vários outros. Assim resolvi abandonar a ideia da máscara, por não saber como aplicar uma que fosse "mutável" (provavelmente há como fazer isso, mas não consegui resolver com essa abordagem). 

Outro problema que identifiquei foi que o domínio pesquisado deveria estar no padrão www.domain.com, não podendo ter o http:// ou https://. Para resolver isso, dentro do else daquele if lá de cima 👆, verifiquei se a pesquisa começava com http com .startsWith('http'). Caso a verificação retorne true então a pesquisa seria tratada da seguinte forma: 
 const HTTPRegex = /^https?:\/\//;
 const httpString = data.search.match(HTTPRegex) //caso o match seja true, retorna um objeto onde o primeiro elemento é exatamente o resultado da regex acima, neste caso http:// ou https://;
 const httpLength = httpString?.[0].lenght // aqui fica armazenado o número de caracteres da const httpString;
 por fim, usei o httpLength para "cortar" a string da pesquisa, utilizando .slice(httpLength) à string. Desta forma, caso o usuário pesquise, por exemplo, por https://www.facebook.com a string utilizada na requisição a API seria apenas www.facebook.com. 

Com o value do input da pesquisa já sendo tratado, agora era hora de salvar response.data em um estado que criei chamado user (as vezes eu não penso em um nome de variável bom e vai essas coisas feias mesmo hahaha 🤪). Essa parte foi simples, consegui salvar no estado de boa, e conforme ia realizando novas pesquisas os dados iam sendo salvos e exibidos em tela, porém o mapa continuava estático. Na verdade, o mapIcon mudava de local, mas o centro do mapa não. 

Após revisitar o código do NWL#3 não achei o problema, resolvi ir procurar na documentação do React-Leaflet e descobri que na sua versão 3, o <MapContainer> agora serve como um provider que habilita o uso de alguns hooks, entre eles o useMap(), que por sua vez possibilita usar o .flyTo(), que serve para voar (tem toda uma animação bacana para isso) para uma nova localização. Para usar o useMap() , tive que mover o <TileLayer> e o <Marker> para um novo componente que foi chamado dentro do <MapContainer> . Nomeei esse novo componente de <MapContent> , e fiz ele receber de seu componente pai uma props chamada position, que nada mais é do que um objeto composto pelas propriedades lat: number e lng: number, já no formato correto para passar como parâmetro para aquela função flyTo(). Com isso o mapa também já estava funcionando. 

Até aqui a aplicação já estava ficando bem funcional. Resolvi passar um try catch dentro do handleSubmit para que caso algum erro ocorresse fosse renderizado na tela um aviso de erro, definido aqui como um genérico 'Invalid IP address or Domain!'. Vale ressaltar que no layout disponibilizado pelo FrontEndMentor não há um estado para exibir erros ou algo assim, então decidi por exibir esse erro dentro de um <span> dentro do componente SearchBar. Esse <span> então recebeu a propriedade position: absolute para ficar logo acima do container da SearchBar , que passou a ter position: relative. Assim caso um erro surja ao realizar uma pesquisa, ele será exibido logo acima da barra de
pesquisa em um vermelho de baixa saturação. 

Bem, esse é apenas meu primeiro texto e a ideia é que ele
sirva como um diário para que eu possa medir minha evolução. De forma alguma esse texto é para ser encarado como um tutorial ou algo semelhante, é apenas um registro de como eu penso para resolver os problemas hoje.

Github do projeto: https://github.com/GuihLeme/ip-address-tracker
Site: https://ip-address-tracker-taupe-six.vercel.app/

Top comments (0)