Desafio do devChallenges.io onde o principal aprendizado foi trabalhar com uma quantidade maior e mais complexa de dados de uma API.
Aproveitei para utilizar ferramentas como Next.js, React e TypeScript para aprimorar meus conhecimentos.
World Ranks é uma página de classificação de países, onde a lista de países é adquirida através da API REST Countries. A página permite pesquisa, classificação e filtragem, além de exibir detalhes individuais de cada país.
- Classificação dos países por nome, população ou área (km²).
- Filtragem por diversas regiões: Américas, Antártida, Ásia, Europa ou Oceania.
- Filtragem por países membros das Nações Unidas.
- Filtragem por países independentes.
- Pesquisa/filtragem de países por nome, região ou sub-região.
- Exibição do número total de países encontrados.
- Visualização detalhada de um país em sua página específica.
- Exibição de informações como população, área, capital, moeda e idioma na página do país.
- Exibição de países vizinhos com links para suas respectivas páginas.
Este projeto me ajudou a praticar e aprimorar meus conhecimentos em tecnologias como React
, Next.js
, TypeScript
e Tailwind CSS
. A seguir, algumas abordagens e aprendizados adquiridos:
Nexts.js
Utilizei pela primeira vez alguns hooks do next/navigation
, como usePathname
, useRouter
e useSearchParams
, que me permitiram controlar a navegação das páginas, recuperar o caminho da URL e acessar os dados passados por parâmetros.
No componente search-input.tsx
, utilizei o hook useDebouncedCallback
, que atrasa a execução de uma função pelo tempo especificado (neste caso, 300 milissegundos). Isso foi útil para evitar requisições excessivas de renderização do componente data-table.tsx
. Segue o código:
export default function SearchInput({ placeholder }: { placeholder: string }) {
const searchParams = useSearchParams();
const pathname = usePathname();
const { replace } = useRouter();
const handleSearch = useDebouncedCallback((term: string) => {
const params = new URLSearchParams(searchParams);
if (term) {
params.set("search", term);
} else {
params.delete("search");
}
replace(`${pathname}?${params.toString()}`);
}, 300);
return (
...
<input
type="text"
placeholder={placeholder}
onChange={(e) => {
handleSearch(e.target.value);
}}
defaultValue={searchParams.get("search")?.toString()}
...
Tailwind CSS
Com o uso contínuo, percebi que o Tailwind CSS
torna a estilização mais ágil. Também utilizei o prettier-plugin-tailwindcss
, que organiza as classes automaticamente ao executar o script lint:fix
.
React
Utilizei createContext
pela primeira vez para resolver um problema importante: passar a quantidade de países encontrados do componente data-table.tsx
para o componente pai page.tsx
, que é renderizado no servidor (SSR). Como não poderia usar useState
e useEffect
, criei um contexto countries-found.tsx
e um novo componente client-side countries-found.tsx
para gerenciar essa informação. Segue a implementação:
context/countries-found.tsx
"use client";
import { createContext, useState, useContext } from "react";
interface CountriesFoundContextProps {
countriesFound: number;
setCountriesFound: React.Dispatch<React.SetStateAction<number>>;
}
const CountriesFoundContext = createContext<CountriesFoundContextProps>({
countriesFound: 0,
setCountriesFound: () => {},
});
export const CountriesFoundProvider = ({ children }) => {
const [countriesFound, setCountriesFound] = useState<number>(0);
return (
<CountriesFoundContext.Provider
value={{ countriesFound, setCountriesFound }}
>
{children}
</CountriesFoundContext.Provider>
);
};
export const useCountriesFound = () => useContext(CountriesFoundContext);
ui/countries-found.tsx
"use client";
import { useCountriesFound } from "../context/countries-found";
export default function ContriesFound() {
const { countriesFound } = useCountriesFound();
return <h2 className="font-medium">Found {countriesFound} countries</h2>;
}
ui/data-table.tsx
"use client";
import { useCountriesFound } from "../context/countries-found";
...
export default function DataTable({
search,
un,
independent,
sortBy,
region,
countries,
}: DataTableProps) {
const { setCountriesFound } = useCountriesFound();
useEffect(() => {
setCountriesFound(filteredCountries.length);
}, [filteredCountries]);
...
}
app/page.tsx
import { CountriesFoundProvider } from "./context/countries-found";
...
return (
<main>
<section className="flex flex-col justify-center gap-6 rounded-xl border border-zinc-800 bg-zinc-900 px-3 py-6 md:px-6">
<CountriesFoundProvider>
<header className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
<CountriesFound />
...
</header>
<div className="flex flex-col gap-6 md:flex-row">
...
<section className="overflow-x-auto md:basis-full">
<DataTable
search={search}
un={un}
independent={independent}
sortBy={sortBy}
region={region}
countries={countries}
/>
</section>
</div>
</CountriesFoundProvider>
</section>
</main>
);
}
TypeScript / Axios
Neste projeto, utilizei axios
para fazer requisições à API e aprimorei meus conhecimentos em TypeScript
, definindo a estrutura dos dados no arquivo lib/definition.ts
:
export type Country = {
name: {
common: string;
};
region: string;
subregion?: string;
population: number;
area: number;
unMember: boolean;
independent: boolean;
flags: {
svg: string;
};
cca3: string;
};
import axios from "axios";
export async function fetchAllCountries() {
try {
const response = await axios.get(
"https://restcountries.com/v3.1/all?sort=population",
);
return response.data;
} catch (error) {
console.error(error);
throw new Error("Error to fetch all countries.");
}
}
# Clone este repositório
$ git clone git@github.com:m4rcone/world-ranks.git
# Acesse a pasta do projeto no seu terminal/cmd
$ cd world-ranks
# Instale as dependências
$ npm install
# Execute a aplicação em modo de desenvolvimento
$ npm run dev
# A aplicação será aberta na porta:3000 - acesse http://localhost:3000
Veja o arquivo: package.json