npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nuskin/address-lookup

v1.1.2

Published

React component for address autocomplete and validation using Smarty services

Downloads

1,225

Readme

@nuskin/address-lookup

React address entry component with:

  • Smarty autocomplete (US + international)
  • optional Smarty verification
  • Contentstack-backed form definitions
  • required-field and email-format validation state
  • localized field labels (Contentstack or parent-provided)

Install

Using npm:

npm add @nuskin/address-lookup

Using yarn:

yarn add @nuskin/address-lookup

Exports

import AddressLookup, {
  normalizeAddressForm,
  SUPPORTED_COUNTRIES
} from '@nuskin/address-lookup'
  • AddressLookup (default + named export)
  • normalizeAddressForm and DEFAULT_ADDRESS_FORM for parent-side form-definition reuse
  • SUPPORTED_COUNTRIES for the list of supported ISO2 market codes

Basic usage

import { useState } from 'react'
import AddressLookup from '@nuskin/address-lookup'

export default function ShippingAddress() {
  const [validationRequestId, setValidationRequestId] = useState(0)
  const [latest, setLatest] = useState(null)

  const handleSave = () => {
    // Triggers child to show validation UI + emit validation-request callback
    setValidationRequestId((n) => n + 1)
  }

  return (
    <>
      <AddressLookup
        country='US'
        language='en'
        requireValidation={true}
        validationRequestId={validationRequestId}
        onAddressSelect={setLatest}
      />

      <button onClick={handleSave}>Save</button>
      <pre>{JSON.stringify(latest, null, 2)}</pre>
    </>
  )
}

Props

AddressLookup props:

  • onAddressSelect: (payload) => void
  • country?: string (default: 'US')
  • language?: string (default: 'en')
  • address?: object | null initial form values, including optional verification metadata
  • requireValidation?: boolean (default: true)
  • showManualValidateButton?: boolean (default: false)
  • validationRequestId?: number (default: 0)
  • strings?: object | null optional label overrides
  • formDefinition?: object | null optional normalized form-definition override

strings shape

If passed, these labels are used instead of loading from Contentstack.

The component accepts either:

  1. a flat normalized label object
  2. a raw Contentstack-style object with grouped validation strings

Flat normalized example:

{
  address1Label: 'Street Address',
  address2Label: 'Apartment, suite, etc.',
  cityLabel: 'City',
  districtLabel: 'District',
  regionLabel: 'Region',
  postalCodeLabel: 'Postal Code',
  invalidEmail: 'Invalid email address.',
  fieldRequired: 'This field is required.',
  invalidPostalCode: 'Invalid postal code.',
  rateLimited: 'Too many requests. Please wait a moment and try again.',
  switchToAutocomplete: 'Switch to Autocomplete',
  switchToManualEntry: 'Switch to Manual Entry',
  addressLocation: 'Address location',
  openInMaps: 'Open in Google Maps',
  usVerified: 'Verified',
  usVerifiedMessage: 'This address is fully verified.',
  usNoResults: 'We couldn’t verify this address. Check the address and try again.',
  intlVerifiedTitle: 'Verified',
  intlVerifiedMessage: 'This address is verified.',
  intlNoResults: 'We couldn’t verify this address. Check the address and try again.'
}

Raw Contentstack-style grouped example:

{
  address1Label: 'Street Address',
  address2Label: 'Apartment, suite, etc.',
  cityLabel: 'City',
  districtLabel: 'District',
  regionLabel: 'Region',
  postalCodeLabel: 'Postal Code',
  invalidPostalCode: 'Invalid postal code.',
  fieldRequired: 'This field is required.',
  rateLimited: 'Too many requests. Please wait a moment and try again.',
  switchToAutocomplete: 'Switch to Autocomplete',
  switchToManuelEntry: 'Switch to Manual Entry',
  addressLocation: 'Address location',
  openInMaps: 'Open in Google Maps',
  smartyUsValidationStrings: {
    usVerified: 'Verified',
    usVerifiedMessage: 'This address is fully verified.',
    usMissingSecondaryTitle: 'Apartment or suite needed',
    usMissingSecondaryMessage: 'The main address is valid, but a unit number is required.',
    usInvalidSecondaryRequiredTitle: 'Apartment or suite not recognized',
    usInvalidSecondaryRequiredMessage: 'The street address is valid, but the unit number was not recognized and is required.',
    usInvalidSecondaryOptionalTitle: 'Unit not recognized',
    usInvalidSecondaryOptionalMessage: 'The main address is valid, but the unit number was not recognized.',
    usVacantTitle: 'Verified, but marked vacant',
    usVacantMessage: 'This address is valid but marked as vacant.',
    usNoStatTitle: 'Verified with caution',
    usNoStatMessage: 'This address is valid, but USPS indicates limited delivery status.',
    usNotConfirmedTitle: 'Not verified',
    usNotConfirmedMessage: 'This address could not be confirmed.',
    usNoResults: 'We couldn’t verify this address. Check the address and try again.'
  },
  smartyIntlValidationStrings: {
    intlVerifiedTitle: 'Verified',
    intlVerifiedMessage: 'This address is verified.',
    intlVerifiedSubBuildingChangedTitle: 'Verified with unit changes',
    intlVerifiedSubBuildingChangedMessage: 'This address is verified, but the apartment or suite information was changed.',
    intlPartialTitle: 'Partially verified',
    intlPartialMessage: 'This address could only be partially verified.',
    intlAmbiguousTitle: 'Multiple matches found',
    intlAmbiguousMessage: 'Multiple possible matches were found for this address.',
    intlNotVerifiedTitle: 'Not verified',
    intlNotVerifiedMessage: 'This address could not be verified.',
    intlNoResults: 'We couldn’t verify this address. Check the address and try again.'
  }
}

Those grouped validation strings are normalized internally into the flat label shape used by the component.

For Contentstack-backed root fields, note that switch_to_manuel_entry is normalized to switchToManualEntry in the component label object.

formDefinition shape

If passed, this is used instead of loading a form definition inside AddressLookup.

{
  addressElements: [
    {
      field: 'address1',
      width: 'full',
      editable: true,
      maxLength: 40,
      required: true,
      apiMappings: {
        autocomplete: 'street',
        verification: 'formatted.address1'
      }
    }
  ],
  lookupField: 'address1',
  requiredFields: ['address1'],
  postalCodeRegex: /^\d{5}$/,
  verifyAfterAutocomplete: false,
  wrapAddress1ToAddress2: false
}

Notes:

  • apiMappings is currently used on international forms
  • valid verification mapping targets include direct Smarty paths like components.locality and derived targets like formatted.address1 / formatted.address2
  • US field mapping remains code-driven

If the parent is already loading form definitions from Contentstack, it can normalize them once and pass them through:

import { normalizeAddressForm } from '@nuskin/address-lookup'

Incoming address verification

The optional address prop can include verification metadata:

{
  address1: '123 Main St',
  city: 'Provo',
  region: 'UT',
  postalCode: '84604',
  verification: {
    status: 'verified' | 'autocomplete' | 'unverified',
    source: 'smarty-us' | 'smarty-intl',
    verifiedAt: '2026-05-01T15:01:07.905Z',
    manuallyEdited: true,
    editedFields: ['address2']
  }
}

Behavior:

  • incoming verified addresses are accepted as-is
  • incoming autocomplete addresses are accepted as-is
  • incoming unverified addresses are auto-verified once when requireValidation=true and enough address data is present
  • if that auto-verification returns exactly one strong result, the component applies it automatically without user interaction
  • after verification/autocomplete, minor editable-field changes preserve the current verification status and mark the address as manually edited
  • major editable-field changes downgrade the address back to unverified

Major-field rule:

  • lookupField is always major
  • address1, city, region, and postalCode are major only when they are required in the resolved form definition
  • fields like address2 and district remain minor by default

Callback payloads

onAddressSelect(payload) emits different type values:

  • autocomplete
    • emitted when a suggestion is selected and mapped into form fields
  • field-change
    • emitted on debounced manual edits
  • validation
    • emitted when user selects a verification candidate
  • validation-request
    • emitted when parent increments validationRequestId
    • reports current status only; does not trigger verification API calls

Common payload shape:

{
  type: 'autocomplete' | 'field-change' | 'validation' | 'validation-request',
  address: {
    ...,
    verification?: {
      status: 'verified' | 'autocomplete' | 'unverified',
      source?: string,
      verifiedAt?: string,
      manuallyEdited?: boolean,
      editedFields?: string[]
    }
  },
  field?: string,
  value?: string,
  verification?: object,
  isDeliverable?: boolean,
  isValidEmail?: boolean,
  status: {
    formAvailable: boolean,
    verification: 'unverified' | 'autocomplete' | 'verified',
    manualChanges?: {
      manuallyEdited: true,
      editedFields: string[]
    },
    needsValidation: boolean,
    completeness: 'complete' | 'incomplete',
    missingFields: string[],
    mode: 'autocomplete' | 'manual',
    postalCode: {
      applicable: boolean,
      required: boolean,
      value: string,
      isValid: boolean | null
    },
    email: {
      applicable: boolean,
      required: boolean,
      value: string,
      isValid: boolean | null
    }
  }
}

For verified US and international validation results, address may also include:

{
  latitude: 48.8566,
  longitude: 2.3522
}

These are returned from Smarty verify response metadata and are included when a validation candidate is selected.

When Google Maps is loaded by the parent app, the component can use these coordinates to show an in-app map modal from the address action row.

Validation behavior

  • Required fields come from the resolved form definition
  • status.formAvailable is false when no form definition is available for the selected country
  • status.needsValidation is true when:
    • requireValidation=true
    • mode is manual
    • address has not been verified yet
  • Required errors are shown when:
    • field loses focus, or
    • parent triggers validation via validationRequestId
  • UI for invalid required/email fields:
    • red input border
    • aria-invalid="true"
  • Email format is validated when email has a non-empty value
  • status.completeness is incomplete when email format is invalid
  • Validation messages use localized strings when provided:
    • fieldRequired
    • invalidPostalCode
    • invalidEmail
    • rateLimited
  • Safe defaults are used when those validation strings are missing
  • verification results are cached briefly by exact request key:
    • US validation cache: 60 seconds
    • intl validation cache: 60 seconds
  • failed validation requests are not cached

Autocomplete and manual mode

  • Lookup field comes from the resolved form definition
    • example: JP can use postalCode
  • intl verification and map-triggered verification also use the configured lookupField, not always address1
  • Typing in lookup creates a draft
  • Clicking outside while suggestions are open cancels draft and restores prior committed values
  • Manual mode:
    • with requireValidation=true, user can run verification explicitly
    • with requireValidation=false, no verification API call is made
    • parent validationRequestId returns current status but does not perform verification
    • optional showManualValidateButton renders a compact Validate Address button next to the lookup field actions

Localization

When strings is not passed, labels load from Contentstack via @nuskin/contentstack-lib and are cached by locale key:

  • cache key format: ${language}_${country}

When formDefinition is not passed, the address form definition also loads from Contentstack.

If no form definition is available after loading, the component shows:

  • No form available for this country: US
  • No form available for this country: {countryCode}

Styling and Overrides

The component now ships with a shared stylesheet:

Parent applications can skin the component in two ways:

  1. Override exported class names in parent CSS
  2. Override CSS variables on the root .ns-address-lookup element

Common class names:

  • .ns-address-lookup
  • .ns-address-lookup__panel
  • .ns-address-lookup__form
  • .ns-address-lookup__field
  • .ns-address-lookup__field--with-error
  • .ns-address-lookup__label-row
  • .ns-address-lookup__label
  • .ns-address-lookup__actions
  • .ns-address-lookup__toggle
  • .ns-address-lookup__input
  • .ns-address-lookup__input--readonly
  • .ns-address-lookup__input--invalid
  • .ns-address-lookup__input--loading
  • .ns-address-lookup__error-text
  • .ns-address-lookup__message
  • .ns-address-lookup__spinner
  • .ns-address-lookup__validation
  • .ns-address-lookup__button
  • .ns-address-lookup__button--compact
  • .ns-address-lookup__list
  • .ns-address-lookup__list--suggestions
  • .ns-address-lookup__list--validation
  • .ns-address-lookup__list--validation-inline
  • .ns-address-lookup__list-item
  • .ns-address-lookup__list-item--divided
  • .ns-address-lookup__list-button
  • .ns-address-lookup__list-title
  • .ns-address-lookup__list-subtitle

Supported CSS variables on .ns-address-lookup:

  • --nsal-text-color
  • --nsal-muted-text
  • --nsal-link-color
  • --nsal-border-color
  • --nsal-error-color
  • --nsal-surface-color
  • --nsal-disabled-surface
  • --nsal-validation-surface
  • --nsal-validation-border
  • --nsal-shadow
  • --nsal-radius
  • --nsal-field-gap
  • --nsal-field-gutter
  • --nsal-error-gap
  • --nsal-input-padding-y
  • --nsal-input-padding-x

Example:

.checkout-address .ns-address-lookup {
  --nsal-border-color: #cbd5e1;
  --nsal-error-color: #b91c1c;
  --nsal-radius: 8px;
  --nsal-field-gutter: 16px;
}

.checkout-address .ns-address-lookup__button {
  background: #111827;
}

Development

Run local test app:

npm run dev

Run checks:

npm run lint
npm test -- --runInBand
npm run build

License

MIT