JavaScript

Estado e cidade

Escolha um estado e as cidades dele carregam sob demanda, com Brazilian Utils em React, Angular, Vue e JavaScript puro.

Um guia da biblioteca JavaScript. Arquivo original

Funções usadas: Municípios: Listar, Estados (UF): Listar

Escolha um estado e as cidades dele preenchem o segundo select. Escolha o framework: cada exemplo roda o código logo abaixo dele, que pode ser copiado do jeito que está.

O ponto aqui é quando cada tabela é carregada, e a resposta é: quando alguém abre o select que a mostra. Nem com a página, nem ao escolher um estado. Num formulário em que a cidade vem preenchida de outro lugar, ou é deixada em branco, os 76 KB de municípios nunca são buscados. Os estados são 27 linhas, 2,2 KB; os municípios são 5.571, 76 KB. Cada utilitário é um subpath, então await import("@brazilian-utils/brazilian-utils/get-municipalities") busca essa tabela e nada mais. Cada opção usa o code IBGE do município como valor e o name como rótulo, já que os nomes se repetem entre estados (existem dois "Pau D'Arco"). O bundler transforma isso num chunk separado, e o browser busca uma vez e guarda, então só a primeira abertura espera.

Um hook por lista, cada um buscando sua tabela quando o onFocus avisa que o select foi aberto. As cidades são de um estado, então o que foi carregado só vale como as cidades da tela enquanto aquele estado for o escolhido:

Carregando o exemplo…

import { useId, useState } from "react";
import { useCitiesOfState } from "./use-cities-of-state";
import { useStates } from "./use-states";

export function StateCity() {
  const id = useId();
  const [state, setState] = useState("");
  const { states, loading: loadingStates, load: loadStates } = useStates();
  const { cities, loading: loadingCities, load: loadCities } = useCitiesOfState(state);

  return (
    <>
      <label htmlFor={id}>State</label>
      {/* Opening the select is what says the list is wanted, so that is when it is fetched. */}
      <select
        id={id}
        value={state}
        aria-busy={loadingStates}
        onFocus={loadStates}
        onChange={(event) => setState(event.currentTarget.value)}
      >
        <option value="">{loadingStates ? "Loading the states…" : "Pick a state"}</option>
        {states.map((current) => (
          <option key={current.code} value={current.code}>
            {current.name}
          </option>
        ))}
      </select>

      <label htmlFor={`${id}-city`}>City</label>
      <select id={`${id}-city`} disabled={!state} aria-busy={loadingCities} onFocus={loadCities}>
        <option value="">{loadingCities ? "Loading the cities…" : "Pick a city"}</option>
        {cities.map((city) => (
          <option key={city.code} value={city.code}>
            {city.name}
          </option>
        ))}
      </select>
    </>
  );
}

Um resource sem nada para perguntar espera, que é onde os dois começam; abrir o select dá a ele o que perguntar. As cidades são do estado para o qual foram pedidas, então escolher outro estado devolve esse resource à espera:

Carregando o exemplo…

Carregando o código…

Um composable por lista, cada um buscando sua tabela quando o @focus avisa que o select foi aberto. As cidades são de um estado, então o que foi carregado só vale como as cidades da tela enquanto aquele estado for o escolhido:

Carregando o exemplo…

Carregando o código…

Sem build: salve como um arquivo .html e abra. Cada subpath é um módulo próprio no CDN, e o import() dentro de um listener de focus busca esse módulo na primeira vez que o select é aberto.

Carregando o exemplo…

state-city.html

Carregando o código…

A introdução lista todos os utilitários que embutem uma tabela e valem um subpath próprio.