npm package: @loadsmart/react-location-select
Description: Location Select component, based on Miranda's Select, for React applications.
Author: Loadsmart
Latest version: 6.0.1

# `LocationSelect`

This project belongs to Loadsmart's front-end team.

This is a spin-off of Miranda [React] `Select` powered with Google Autocomplete, Places, and Geocoder capabilities.

## Getting started

To install it, run:

```bash
npm add @loadsmart/react-location-select
```

or

```bash
yarn add @loadsmart/react-location-select
```

Please, make sure that you also have all the peer dependencies listed in the [package.json](./package.json) installed.

## How to use

**Important**:

- You need to provide the `googleMapsAPIKey` prop for the `Select` to be visible and ready for location selection.
- If you add a custom datasource, make sure its reference is stable (i.e., you won't generate a new reference
  unnecessarily) so the `Select` can appropriately optimize its data source management.
- If you add a custom `config` for the Google Maps datasource, make sure its reference is stable (i.e., you won't
  generate a new reference unnecessarily) so the `Select` can appropriately optimize its data source management.

```jsx
import LocationSelect from '@loadsmart/react-location-select';

<LocationSelect {...props} googleMapsAPIKey={'<Your Google Maps API key>'} />;
```

The Google Maps datasource will always be included. You can customize how it fetches locations, if needed, by
providing the `config` prop. This is the default `config`:

```js
const DEFAULT_CONFIG = {
	// Source: https://developers.google.com/maps/documentation/places/web-service/supported_types#table3
	types: 'cities',
	// Source: https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service#ComponentRestrictions
	restrictions: { country: ['us', 'ca'] },
	// Source: https://developers.google.com/maps/documentation/javascript/reference/3.45/places-service#PlaceResult
	fields: ['address_components', 'place_id'],
};
```

### Jest Configuration

If you are using Jest, you need to add this library to `transformIgnorePatterns` in your Jest configuration to ensure it is properly transformed. Example:

```json
{
	"transformIgnorePatterns": [
		"node_modules/(?!@loadsmart/react-location-select)"
	]
}
```

### Mocking the returned locations

You most certainly will need unit tests around the code that will use the `LocationSelect` component. We export a mock factory to help returning the locations you want to work on your tests.

```js
import {
	GoogleMapsMock,
	GoogleMapsMockFactory,
} from '@loadsmart/react-location-select/dist/testing';

// Save the original google reference to reset it after the test is completed
const originalWindowGoogle = window.google;

// Generate mock data using a default info.
// e.g. { "address": "New York, NY, United States" }
window.google = GoogleMapsMock();

// Or generate mock data with an overridden info.
window.google = GoogleMapsMockFactory({
	predictionStub(query) {
		return new Promise((resolve) => {
			resolve({
				predictions: [
					{
						description: 'New York, NY, United States',
						id: '7eae6a016a9c6f58e2044573fb8f14227b6e1f96',
						matched_substrings: [
							{
								length: 2,
								offset: 0,
							},
						],
						place_id: 'ChIJOwg_06VPwokRYv534QaPC8g',
						reference: '...',
						structured_formatting: {
							main_text: 'New York, NY, United States',
							main_text_matched_substrings: [
								{
									length: 2,
									offset: 0,
								},
							],
							secondary_text: '',
						},
						terms: [
							{
								offset: 0,
								value: 'New York',
							},
							{
								offset: 10,
								value: 'NY',
							},
							{
								offset: 14,
								value: 'United States',
							},
						],
						types: ['locality', 'political', 'geocode'],
					},
				],
			});
		});
	},
	geocodeStub(query, callback) {
		if (query.address === '' || query.placeId === '') {
			callback([], 'ZERO_RESULTS');
			return;
		}

		callback(
			[
				{
					address_components: [
						{
							long_name: 'New York',
							short_name: 'New York',
							types: ['locality', 'political'],
						},
						{
							long_name: 'New York',
							short_name: 'NY',
							types: ['administrative_area_level_1', 'political'],
						},
						{
							long_name: 'United States',
							short_name: 'US',
							types: ['country', 'political'],
						},
					],
					formatted_address: 'New York, NY, USA',
					geometry: {
						bounds: {
							south: 40.4773991,
							west: -74.25908989999999,
							north: 40.9175771,
							east: -73.70027209999999,
						},
						location: { lat: 40.7127753, lng: -74.0059728 },
						location_type: 'APPROXIMATE',
						viewport: {
							south: 40.4773991,
							west: -74.25908989999999,
							north: 40.9175771,
							east: -73.70027209999999,
						},
					},
					place_id: 'ChIJOwg_06VPwokRYv534QaPC8g',
					types: ['locality', 'political'],
				},
			],
			'OK',
		);
	},
	placesStub(query, callback) {
		callback(
			{
				address_components: [
					{
						long_name: 'Newark',
						short_name: 'Newark',
						types: ['locality', 'political'],
					},
					{
						long_name: 'Essex County',
						short_name: 'Essex County',
						types: ['administrative_area_level_2', 'political'],
					},
					{
						long_name: 'New Jersey',
						short_name: 'NJ',
						types: ['administrative_area_level_1', 'political'],
					},
					{
						long_name: 'United States',
						short_name: 'US',
						types: ['country', 'political'],
					},
				],
				place_id: 'ChIJHQ6aMnBTwokRc-T-3CrcvOE',
				geometry: {
					location: {
						lat: () => 0,
						lng: () => 0,
					},
				},
				html_attributions: [],
			},
			'OK',
		);
	},
});

// Reset the mock after the test is completed.
afterAll(() => {
	window.google = originalWindowGoogle;
});
```

### Using the logger

If you need to debug any behavior related to the LocationSelect when consuming the `@loadsmart/react-location-select` library,
you can enable component logging by setting the global variable `window.__LOCATION_SELECT_ENABLE_LOGGING__`.

In the browser console, run the following command:

```js
window.__LOCATION_SELECT_ENABLE_LOGGING__ = true;
```

During development inside the library, to log from within the components, please use the `utils/logger.ts` file.

Logging is disabled (not shown) by default. To enable it, set the `STORYBOOK_ENABLE_LOGGING` environment variable to `true` in your `.env` file.

```bash
STORYBOOK_ENABLE_LOGGING=true
```

### Using a custom parser

You can pass a custom parser and manipulate the returned data if you need a different result than the default `LocationSelect` config.

```tsx
const customParser = (query: string, result: GeocodedLocation) => {
	return result;
};

<LocationSelect {...args} config={{ parser: customParser }} />;
```

### Using a custom adapter

If you need a different adapter to display the select options, you can pass a custom adapter and change the data that is returned.

```jsx
const customAdapter = {
	getKey(location) {
		return location?.address;
	},
	getLabel(location) {
		return location?.address;
	},
};

<LocationSelect {...args} config={{ adapter: customAdapter }} />;
```

### Using on a TypeScript project

You can import the types from this package.

```ts
import { LocationSelect } from '@loadsmart/react-location-select';
import type {
	Location,
	LocationParser,
	// ... Any other type
} from '@loadsmart/react-location-select';
```

The [@types/google.maps](https://www.npmjs.com/package/@types/google.maps) is installed by this package, don't worry about it.
You can use it without importing or installing anything.

## Contributing

This project is open to contributions. If you want to contribute, please follow the steps below:

### Requirements

1. Node (See the required version at [.nvmrc](.nvmrc)).
2. [Yarn Berry](https://yarnpkg.com/)
   From the [Installation](https://yarnpkg.com/getting-started/install) Yarn docs

   > The preferred way to manage Yarn is by-project and through [Corepack](https://yarnpkg.com/corepack), a tool shipped by default with Node.js. Modern releases of Yarn aren't meant to be installed globally, or from npm.
   - Start by enabling Corepack, if it isn't already; this will add the yarn binary to your PATH:

   ```sh
   corepack enable
   ```

   or if you use Volta. Run the following.

   ```sh
   npm install -g corepack

   # Specifying an explicit install-directory makes corepack overwrite volta's yarn shims, which is what we want
   corepack enable --install-directory ~/.volta/bin
   ```

   > The ["packageManager"](https://nodejs.org/api/packages.html#packagemanager) field defines which package manager is expected to be used when working on the current project. It can be set to any of the supported package managers, and will ensure that your teams use the exact same package manager versions without having to install anything else other than Node.js.

> To successfully run this project on your local environment and obtain data from Compass, please adhere to the following steps:

### Setting up the environment

- Change the component stories by adding the `compassConfig.APIKey` in `src/location-select/location-select.stories.tsx`.
  - You can learn how to get a key [here](https://loadsmart.atlassian.net/wiki/spaces/engineering/pages/3181936667/Compass+Obtaining+an+API+Key)

```diff
export const Demo: Story = {
  args: {
    name: 'location',
    googleMapsAPIKey: '<YOUR GOOGLE MAPS API KEY GOES HERE>',
    multiple: true,
    disableMapsLoading: false,
    googleMapsAPIVersion: '3.51',
+   compassConfig: {
+    baseURL: 'https://api.staging.loadsmart.com',
+    APIKey: '<API_KEY>',
+    },
    },
    render: DemoRenderer,
};

```

> The `dev` command runs on port 8080, which has the necessary permissions to establish requests to Compass in stating.