Created
April 14, 2024 19:10
-
-
Save AmitMirgal/34473f99d1e3c9037c03a6941619f25b to your computer and use it in GitHub Desktop.
React component that uses forward geocoding from the Mapbox service. You can copy and paste this into your app code, styled with TailwindCSS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This example requires some changes to your config: | |
``` | |
import tailwindCSSForms from "@tailwindcss/forms"; | |
// tailwind.config.js | |
export default { | |
// ... | |
plugins: [ | |
// ... | |
tailwindCSSForms, | |
], | |
} | |
``` | |
*/ | |
import { | |
useState, | |
useDeferredValue, | |
useEffect, | |
startTransition, | |
memo, | |
} from "react"; | |
import { Combobox } from "@headlessui/react"; | |
import { ChevronUpDownIcon, CheckIcon } from "@heroicons/react/20/solid"; | |
import { clsx } from "clsx"; | |
type AddressType = { | |
properties: { | |
name: string; | |
full_address: string; | |
}; | |
[key: string]: AddressType | unknown; | |
}; | |
function classNames(...classes: Array<string | boolean>) { | |
return classes.filter(Boolean).join(" "); | |
} | |
const SearchResults = memo(function SearchResults({ | |
query, | |
}: { | |
query: string; | |
}) { | |
const [address, setAddress] = useState<AddressType[]>([]); | |
const filteredAddress = | |
query === "" | |
? address | |
: address.filter((address: AddressType) => { | |
return address.properties.name | |
.toLowerCase() | |
.includes(query.toLowerCase()); | |
}); | |
useEffect(() => { | |
const searchPlaces = async () => { | |
const encodedQuery = encodeURIComponent(query); | |
const res = await fetch( | |
`https://api.mapbox.com/search/geocode/v6/forward?q=${encodedQuery}&access_token=${import.meta.env.VITE_MAPBOX_API_KEY}`, | |
); | |
const result = await res.json(); | |
startTransition(() => { | |
setAddress(result?.features); | |
}); | |
}; | |
if (query !== "") { | |
searchPlaces(); | |
} | |
}, [query]); | |
if (query === "") { | |
return null; | |
} | |
if (address.length === 0) { | |
return ( | |
<p className="mt-2"> | |
No matches for <b>"{query}"</b> | |
</p> | |
); | |
} | |
return ( | |
<> | |
{filteredAddress.length > 0 && ( | |
<Combobox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"> | |
{filteredAddress.map((address: AddressType) => ( | |
<Combobox.Option | |
key={address.properties.name} | |
value={address} | |
className={({ active }) => | |
classNames( | |
"relative cursor-default select-none py-2 pl-3 pr-9", | |
active ? "bg-indigo-600 text-white" : "text-gray-900", | |
) | |
} | |
> | |
{({ active, selected }) => ( | |
<> | |
<span | |
className={classNames( | |
"block truncate", | |
selected && "font-semibold", | |
)} | |
> | |
{address.properties.full_address} | |
</span> | |
{selected && ( | |
<span | |
className={classNames( | |
"absolute inset-y-0 right-0 flex items-center pr-4", | |
active ? "text-white" : "text-indigo-600", | |
)} | |
> | |
<CheckIcon className="h-5 w-5" aria-hidden="true" /> | |
</span> | |
)} | |
</> | |
)} | |
</Combobox.Option> | |
))} | |
</Combobox.Options> | |
)} | |
</> | |
); | |
}); | |
export default function AddressLookup({ className }: { className?: string }) { | |
const [query, setQuery] = useState(""); | |
const deferredQuery = useDeferredValue(query); | |
const [selectedAddress, setSelectedAddress] = useState<AddressType | null>( | |
null, | |
); | |
return ( | |
<div className={clsx([className, "col-span-full"])}> | |
<div className="mt-1"> | |
<Combobox | |
as="div" | |
name="address" | |
value={selectedAddress} | |
onChange={setSelectedAddress} | |
> | |
<Combobox.Label className="block text-sm font-medium leading-6 text-gray-900"> | |
Address | |
</Combobox.Label> | |
<div className="relative mt-2"> | |
<Combobox.Input | |
className="w-full rounded-md border-0 bg-white py-1.5 pl-3 pr-10 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" | |
onChange={(event) => setQuery(event.target.value)} | |
displayValue={(address: AddressType) => | |
address?.properties?.full_address | |
} | |
/> | |
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none"> | |
<ChevronUpDownIcon | |
className="h-5 w-5 text-gray-400" | |
aria-hidden="true" | |
/> | |
</Combobox.Button> | |
<SearchResults query={deferredQuery} /> | |
</div> | |
</Combobox> | |
</div> | |
</div> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
AddressLookup
React component that uses forward geocoding from the Mapbox service. You can copy and paste this into your app code, styled with TailwindCSS
Run the following command
# required packages npm install @headlessui/react @heroicons/react clsx @tailwindcss/forms
Update tailwind.config.js
Add the following
@tailwindcss/forms
to your tailwind.config.js file:tailwind.config.js
Tip
Please comment if I missed anything out. Happy coding 🔥