JavaScript

State and city

Pick a state and the cities of that state load on demand, with Brazilian Utils in React, Angular, Vue and plain JavaScript.

A guide from the JavaScript library. Source file

Functions used: Municipalities: List, States (UF): List

Pick a state and its cities fill the second select. Pick the framework: each example runs the code below it, which you can copy as is.

The point of this one is when each table is loaded, and the answer is: when someone opens the select that shows it. Not with the page, and not when a state is picked either. A form where the city is filled in by something else, or left alone, never fetches 76 KB of municipalities. The states are 27 rows, 2.2 KB; the municipalities are 5,571 of them, 76 KB. Each util is its own subpath, so await import("@brazilian-utils/brazilian-utils/get-municipalities") fetches that table and nothing else. Each option takes the IBGE code of the municipality as its value and the name as its label, since names repeat across states (there are two "Pau D'Arco"). A bundler makes it a chunk of its own; the browser fetches it once and keeps it, so only the first opening waits.

One hook per list, each fetching its table when onFocus says the select was opened. The cities belong to a state, so what was loaded counts as the cities on screen only while that state is the one picked:

Loading the demo…

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>
    </>
  );
}

A resource with nothing to ask about waits, which is where both of these start; opening a select gives one something to ask about. The cities are about the state they were asked for, so picking another state puts that resource back to waiting:

Loading the demo…

Loading the code…

One composable per list, each fetching its table when @focus says the select was opened. The cities belong to a state, so what was loaded counts as the cities on screen only while that state is the one picked:

Loading the demo…

Loading the code…

No build step: save it as an .html file and open it. Each subpath is its own module on the CDN, and import() inside a focus listener fetches it the first time the select is opened.

Loading the demo…

state-city.html

Loading the code…

The getting started guide lists every util that embeds a table and is worth a subpath of its own.