React Native Taxi App: Booking Information. Places API
Tags: reactnative, react, testing, googlecloud
Published: 2021-08-14 18:01:41

[Source](https://github.com/crisecheverria/taxi-app-tutorial/tree/part-4)
Let's continue working with the Booking process. We're going to use the same UserScreen component for:

1. Departure Information
2. Booking Information

We already worked on Departure Information on previous parts of this tutorial. For the Booking Information let's start creating a new file for the component inside **src/components/BookingInformation.js**:

```js
// src/components/BookingInformation.js
import React from "react"
import styled from "styled-components/native"
import FeatherIcon from "react-native-vector-icons/Feather"
import { formatPlaceName } from "../utils"
import { usePlace } from "../context/PlacesManager"

const Container = styled.View`
  flex: 1.5;
  background-color: #fff;
  padding-vertical: 20px;
  padding-horizontal: 20px;
`

export const Location = styled.View`
  flex-direction: row;
  align-items: center;
`
const LocationPlaceholder = styled.Text`
  color: #717171;
  font-size: 14px;
  margin-left: 5px;
  font-weight: 600;
`;

const Text = styled.Text`
  color: #000;
  font-size: 16px;
  font-weight: 600;
  margin-left: 5px;
`

export default function BookingInformation() {
  const {
    place: { currentPlace },
  } = usePlace()

  return (
    <Container>
      <Location>
        <FeatherIcon name="map-pin" size={15} color="gray" />
        <Text testID="current-place-description">
          {formatPlaceName(currentPlace.description)}
        </Text>
      </Location>
      
      <FeatherIcon
        name="more-vertical"
        size={15}
        color="gray"
        marginTop={-10}
      />

      <Location>
        <FeatherIcon name="more-vertical" size={15} color="gray" />
        <LocationPlaceholder testID="destination-label">
          Destination address
        </LocationPlaceholder>
      </Location>
    </Container>
  )
}
```
We created a couple of Styled components also imported `usePlace` custom hook from PlacesManager Context Provider to show the selected currentPlace description. The main idea is to show the `BookingInformation` component from UserScreen when we toggle the `Book Now` button from the DepartureInformation component.

I'm going to create another custom hook component for show/hide functionality in our App. For that, let's create a new folder, `taxiApp/src/hooks/index.js`:

```js
// taxiApp/src/hooks/index.js
import {useState} from 'react';

export const useShowState = (initialOpen = false) => {
  const [isOpen, setIsOpen] = useState(initialOpen);

  const onToggle = () => {
    setIsOpen((prevState) => !prevState);
  };

  return [isOpen, onToggle];
};
```
Now, let's use `useShowState` custom hook inside our UserScreen component.

```js
// taxiApp/src/screens/UserScreen.js
/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React, {useEffect, useState} from 'react';
import {StatusBar, Platform, Image} from 'react-native';
import styled from 'styled-components/native';
import MapView, {PROVIDER_GOOGLE} from 'react-native-maps';
import {check, request, PERMISSIONS, RESULTS} from 'react-native-permissions';
import Geolocation from 'react-native-geolocation-service';
import {customStyleMap, MenuButtonLeft} from '../styles';
import FeatherIcon from 'react-native-vector-icons/Feather';
import DepartureInformation from '../components/DepartureInformation';
import Geocoder from 'react-native-geocoding';
import {usePlace} from '../context/PlacesManager';
import {GOOGLE_MAPS_API_KEY} from '../utils/constants';
import marker from '../assets/icons-marker.png';
// Import BookingInformation and useShowState custom hook
import BookingInformation from '../components/BookingInformation';
import {useShowState} from '../hooks';

Geocoder.init(GOOGLE_MAPS_API_KEY, {language: 'en'});

const Container = styled.SafeAreaView`
  flex: 1;
  background-color: #fff;
`;

const mapContainer = {
  flex: 7,
};

const FixedMarker = styled.View`
  left: 50%;
  margin-left: -16px;
  margin-top: -125px;
  position: absolute;
  top: 50%;
`;

const markerStyle = {
  height: 36,
  width: 36,
};

const UserScreen = ({navigation}) => {
  const [location, setLocation] = useState(null);
  const {place, dispatchPlace} = usePlace();
  // Create a local state using the custom Hook
  const [showBooking, toggleShowBookingViews] = useShowState(false);

  const handleLocationPermission = async () => {
    let permissionCheck = '';
    if (Platform.OS === 'ios') {
      permissionCheck = await check(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE);

      if (permissionCheck === RESULTS.DENIED) {
        const permissionRequest = await request(
          PERMISSIONS.IOS.LOCATION_WHEN_IN_USE,
        );
        permissionRequest === RESULTS.GRANTED
          ? console.warn('Location permission granted.')
          : console.warn('Location perrmission denied.');
      }
    }

    if (Platform.OS === 'android') {
      permissionCheck = await check(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION);

      if (permissionCheck === RESULTS.DENIED) {
        const permissionRequest = await request(
          PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
        );
        permissionRequest === RESULTS.GRANTED
          ? console.warn('Location permission granted.')
          : console.warn('Location perrmission denied.');
      }
    }
  };

  useEffect(() => {
    handleLocationPermission();
  }, []);

  useEffect(() => {
    Geolocation.getCurrentPosition(
      position => {
        const {latitude, longitude} = position.coords;
        Geocoder.from({
          latitude: latitude,
          longitude: longitude,
        }).then(res => {
          const {
            formatted_address,
            place_id,
            geometry: {
              location: {lat, lng},
            },
          } = res.results[0];
          setLocation({latitude, longitude});
          dispatchPlace({
            type: 'SET_CURRENT_PLACE',
            description: formatted_address,
            placeId: place_id,
            latitude: lat,
            longitude: lng,
          });
        });
      },
      error => {
        console.log(error.code, error.message);
      },
      {enableHighAccuracy: true, timeout: 15000, maximumAge: 10000},
    );
  }, [dispatchPlace]);

  const onRegionChange = ({latitude, longitude}) => {
    Geocoder.from({
      latitude,
      longitude,
    }).then(res => {
      const {
        formatted_address,
        place_id,
        geometry: {
          location: {lat, lng},
        },
      } = res.results[0];

      dispatchPlace({
        type: 'SET_CURRENT_PLACE',
        description: formatted_address,
        placeId: place_id,
        latitude: lat,
        longitude: lng,
      });
    });
  };

  useEffect(() => {
    navigation.setOptions({
      headerLeft: () => (
        <MenuButtonLeft
          onPress={() => navigation.navigate('Menu')}
          testID="modal-menu">
          <FeatherIcon name="menu" size={25} color="#000" />
        </MenuButtonLeft>
      ),
    });
  }, [navigation]);

  return (
    <Container>
      <StatusBar barStyle="dark-content" />
      {location && (
        <MapView
          testID="map"
          style={mapContainer}
          provider={PROVIDER_GOOGLE}
          initialRegion={{
            latitude: location.latitude,
            longitude: location.longitude,
            latitudeDelta: 0.0922,
            longitudeDelta: 0.0421,
          }}
          onRegionChangeComplete={onRegionChange}
          showsUserLocation={true}
          customMapStyle={customStyleMap}
          paddingAdjustmentBehavior="automatic"
          showsMyLocationButton={true}
          showsBuildings={true}
          maxZoomLevel={17.5}
          loadingEnabled={true}
          loadingIndicatorColor="#fcb103"
          loadingBackgroundColor="#242f3e"
        />
      )}

      <FixedMarker testID="fixed-marker">
        <Image style={markerStyle} source={marker} />
      </FixedMarker>
      
      {/* Logic to when to show BookingInformation or DepartureInformation */}
      {showBooking ? (
        <BookingInformation />
      ) : (
        <DepartureInformation toggleShowBookingViews={toggleShowBookingViews} />
      )}
{/* See that we pass toggleShowBookingViews as prop */}
    </Container>
  );
};

export default UserScreen;
```
As you see, we imported `BookingInformation` component and `useShowState` custom hook to `UserScreen`. The custom hook will create a local state that will handle when we show/hide `BookingInformation` and `DepartureInformation`.

We also passed `toggleShowBookingViews` function as prop to DepartureInformation. The reason is to toggle the `Book Now` button and update the `showBooking` state.

Now let's move to `DepartureInformation` and handle the new prop we just passed.

```js
// taxiApp/src/components/DepartureInformation.js
...
export default function DepartureInformation({toggleShowBookingViews}) {
...
  return (
    <Container platform={Platform.OS}>
      ...

      <BookNow>
        <BookNowButton
          onPress={toggleShowBookingViews}
          testID="book-now-button">
          <ButtonText>Book now</ButtonText>
        </BookNowButton>
      </BookNow>
    </Container>
  );
}

DepartureInformation.propTypes = {
  toggleShowBookingViews: PropTypes.func,
};
```
We receive the passed prop `toggleShowBookingViews` and then replace the console.log() function with the new prop function in the `BookNowButton` component. 

So, if everything is fine when you press the `Book Now` button, you should see `BookingInformation` component UI, as shown in the gif below.

![Book Now Button Toggle](https://media.giphy.com/media/3IBnQxtzGntrzhodF7/giphy.gif)

## Add a Booking Information Input

We need to add an input where the user can type the Destination Place. The idea is to have a `Modal` where we display a `TextInput`. Before that, let's add a `Pressable` component that will open the Modal.

```js
// taxiApp/src/components/BookingInformation.js
import React from 'react';
import styled from 'styled-components/native';
import FeatherIcon from 'react-native-vector-icons/Feather';
import {formatPlaceName} from '../utils';
import {usePlace} from '../context/PlacesManager';
// Import custom hook for show/hide elements.
import {useShowState} from '../hooks';

const Container = styled.View`
  flex: 1.5;
  background-color: #fff;
  padding-vertical: 20px;
  padding-horizontal: 20px;
`;

export const Location = styled.View`
  flex-direction: row;
  align-items: center;
`;

const LocationPlaceholder = styled.Text`
  color: #717171;
  font-size: 14px;
  margin-left: 5px;
  font-weight: 600;
`;

const Text = styled.Text`
  color: #000;
  font-size: 16px;
  font-weight: 600;
  margin-left: 5px;
`;

// New Pressable component
const LocationPressable = styled.Pressable`
  flex-direction: row;
  align-items: center;
  margin-bottom: 10px;
`;

// New styled component
const AddDestinationText = styled.Text`
  color: #000;
  font-size: 20px;
  font-weight: 600;
  margin-left: 5px;
`;

// New styled component
const TextRight = styled(Text)`
  margin-left: auto;
`;

export default function BookingInformation() {
  // Add destinationPlace from PlacesManager
  const {
    place: {currentPlace, destinationPlace},
  } = usePlace();
  // Create a local state for toggle a Modal
  const [isModalVisible, togglePlaceModal] = useShowState();

  return (
    <Container>
      <Location>
        <FeatherIcon name="map-pin" size={15} color="gray" />
        <Text testID="current-place-description">
          {formatPlaceName(currentPlace.description)}
        </Text>
      </Location>

      <FeatherIcon
        name="more-vertical"
        size={15}
        color="gray"
        marginTop={-10}
      />

      <Location>
        <FeatherIcon name="more-vertical" size={15} color="gray" />
        <LocationPlaceholder testID="destination-label">
          Destination address
        </LocationPlaceholder>
      </Location>
      {/* Add new components for toggle a Modal */}
      <LocationPressable onPress={togglePlaceModal}>
        <FeatherIcon name="circle" size={15} color="gray" />
        <AddDestinationText testID="destination-place-description">
          {formatPlaceName(destinationPlace.description) || 'Add destination'}
        </AddDestinationText>
        <TextRight>
          <FeatherIcon name="search" size={15} color="#000" />
        </TextRight>
      </LocationPressable>
    </Container>
  );
}
```
We import the custom hook for handle show/hide Modal. Add a couple of new Styled Components, including `Pressable` from React Native. Also, add `destinationPlace` from PlacesManager Context Provider.

### Destination Address Modal

Alright, we need to create a new component called `SearchAddressModal`, inside that Modal we will have a TextInput for search the user destination place. `SearchAddressModal` will be called from the `BookingInformation` component.

#### React Native Modal

We'll use a new package called `react-native-modal` for our Modal component, let's install it:

```js
npm i react-native-modal --save-exact
```

Create a new file inside `taxiApp/src/components/SearchAddressModal.js`:

```js
// taxiApp/src/components/SearchAddressModal.js
import React from 'react';
import {StatusBar, TextInput} from 'react-native';
import styled from 'styled-components/native';
import Modal from 'react-native-modal';
import FeatherIcon from 'react-native-vector-icons/Feather';

const Container = styled.SafeAreaView`
  flex: 1;
`;

const BackButton = styled.TouchableOpacity`
  margin-top: 10px;
`;

const ModalChildrenView = styled.View`
  flex-direction: row;
  align-items: center;
`;

const SearchContainer = styled.View`
  flex-direction: row;
  align-items: center;
`;

const ClearDestinationButton = styled.TouchableOpacity`
  margin-left: auto;
`;

const Input = styled(TextInput)`
  color: #000000;
  font-size: 20px;
  font-weight: 600;
  height: 50px;
  width: 90%;
  padding: 10px;
`;

export default function SearchAddressModal({isModalVisible, toggleModal}) {
  return (
    <Modal
      isVisible={isModalVisible}
      backdropColor="white"
      backdropOpacity={1}
      animationIn="slideInUp"
      testID="search-address-modal">
      <StatusBar barStyle="dark-content" />
      <Container>
        <BackButton testID="back-button" onPress={toggleModal}>
          <FeatherIcon name="arrow-left" size={20} color="gray" />
        </BackButton>

        <ModalChildrenView>
          <SearchContainer>
            <FeatherIcon name="map-pin" size={20} color="gray" />
            <Input
              placeholder="Add destination"
              placeholderTextColor="#000000"
            />
            <ClearDestinationButton testID="clear-button" onPress={() => {}}>
              <FeatherIcon name="x-circle" color="grey" size={20} />
            </ClearDestinationButton>
          </SearchContainer>
        </ModalChildrenView>
      </Container>
    </Modal>
  );
}
```
As you see now, we have a new component that uses Modal from `react-native-modal`. For now, this component doesn't have a local state; instead, we receive a couple of props from the `BookingInformation` component to check the Modal's state and close the Modal.

We added a `TextInput` that for now doesn't work because it doesn't have a state. The local state for the `Input` component will come as prop from `BookingInformation`.

#### Open the Modal from Booking Information

Move into `BookingInformation` and import the new component, also pass the props the new Modal component need to be visible.

```js
// taxiApp/src/components/BookingInformation.js
import SearchAddressModal from './SearchAddressModal';

...
export default function BookingInformation() {
...
  const [isModalVisible, togglePlaceModal] = useShowState();

  return (
    <>
      <Container>
        ...
      </Container>
     {/* Import the new Modal component */}
      <SearchAddressModal
        isModalVisible={isModalVisible}
        toggleModal={togglePlaceModal}
      />
    </>
  );
}
```
As you can see we imported the new Modal component we created and we are rendering outside our `<Container></Container>` component, that's why we use `<></>` before and after Container component. 

We also pass the two props `<SearchAddressModal />` component is expecting in order to show/hide.

```js
 <SearchAddressModal
        isModalVisible={isModalVisible}
        toggleModal={togglePlaceModal}
      />
```

If everything is fine, when you press the "Add destination" component, you should see that the Modal is visible. Inside the Modal, you can close it by pressing the back arrow button.

![Show/Hide Modal](https://media.giphy.com/media/92QSKHKht9qfIO1YRh/giphy.gif)

#### Add local state for Modal Input

Let's add a local state that will make the Input component work, as I mentioned above. This local state will come from `BookingInformation` and passed to the Modal component.

```js
// taxiApp/src/components/BookingInformation.js
import React, {useState} from 'react';
...

export default function BookingInformation() {
  const {
    place: {currentPlace, destinationPlace},
  } = usePlace();
  const [isModalVisible, togglePlaceModal] = useShowState();
 // Input Modal state
  const [newAddress, setNewAddress] = useState(null);

  return (
    <>
      <Container>
        ...
      </Container>

      <SearchAddressModal
        isModalVisible={isModalVisible}
        toggleModal={togglePlaceModal}
     +  newAddress={newAddress}
     +  setNewAddress={setNewAddress}
      />
    </>
  );
}
```
Now, we have to move into `SearchAddressModal` component and receive the two props and use them for the `Input` component.

```js
// taxiApp/src/components/SearchAddressModal.js
...

export default function SearchAddressModal({
  isModalVisible,
  toggleModal,
+ newAddress,
+ setNewAddress,
}) {
  return (
    <Modal
      isVisible={isModalVisible}
      backdropColor="white"
      backdropOpacity={1}
      animationIn="slideInUp"
      testID="search-address-modal">
      <StatusBar barStyle="dark-content" />
      <Container>
        <BackButton testID="back-button" onPress={toggleModal}>
          <FeatherIcon name="arrow-left" size={20} color="gray" />
        </BackButton>

        <ModalChildrenView>
          <SearchContainer>
            <FeatherIcon name="map-pin" size={20} color="gray" />
            <Input
              placeholder="Add destination"
              placeholderTextColor="#000000"
   +          value={newAddress}
   +          onChangeText={text => setNewAddress(text)}
            />
            <ClearDestinationButton
              testID="clear-button"
   +          onPress={() => setNewAddress('')}>
              <FeatherIcon name="x-circle" color="grey" size={20} />
            </ClearDestinationButton>
            </ClearDestinationButton>
          </SearchContainer>
        </ModalChildrenView>
      </Container>
    </Modal>
  );
}
```
After that, we should be able to type inside the Input component. Also, we should be able to clear what we type when we press the "x-circle" button.

### Google Places API

We will use Places API from Google to search for our destination place when we type using the Input component on the Modal screen.

There's a good package for React Native called `react-native-google-places-autocomplete` that you can use, I tested it, and it works fine. It already comes with an Input component.

But, for this project to have more control, I decided to do it step by step.

We'll need to enable *Places API* in our Google Console Cloud project, the same steps we followed for enabling Geocoding API and Maps SDK for Android and iOS.

![Places API](https://i.imgur.com/sxvWbA9.png)

The process is quite simple, we need to fetch from and endpoint passing the Google Maps API Key and the destination. We'll create a new function utility for that inside our `src/utils/index.js` file:

```js
import {GOOGLE_MAPS_API_KEY} from './constants';

...
// This function receive two arguments
export const APIPlaceAutocomplete = (destination, currentPlace) => {
  const URL = `https://maps.googleapis.com/maps/api/place/autocomplete/json?key=${GOOGLE_MAPS_API_KEY}&input=${destination}&location=${currentPlace.latitude},${currentPlace.longitude}&radius=2000`;

  if (destination.length > 0) {
    return fetch(URL)
      .then(resp => resp.json())
      .catch(error => error);
  } else {
    return 'No destination Address provided';
  }
};
```

So, we are fetching from `https://maps.googleapis.com/maps/api/place/autocomplete/json` passing a couple of parameters:

- key
- input
- location
- radius

We have to call this function from the `SearchAddressModal` component passing the parameters it needs to make the successful call to the endpoint.

### Fetch Places API using Lodash Debounce

If we call the Google Places API while we type using the Input component from the Modal, we will make the call every time we type any single word, which is useless and unsuitable for optimization.

That's why we're going to use Debounce from the Lodash library. Install Lodash:

```js
npm i --save-exact lodash
```

Open SearchAddressModal component:

```js
+ import React, {useState, useEffect, useCallback} from 'react';
...
import {debounce} from 'lodash';
import {APIPlaceAutocomplete} from '../utils';

...

export default function SearchAddressModal({
  isModalVisible,
  toggleModal,
  newAddress,
  setNewAddress,
+ currentPlace,
}) {
+  const [predictions, setPredictions] = useState([]);

+  useEffect(() => {
    if (newAddress) {
      debounceSearch(newAddress);
    } else {
      setPredictions([]);
    }
  }, [newAddress, debounceSearch]);

+  const debounceSearch = useCallback(
    debounce(address => {
      APIPlaceAutocomplete(address, currentPlace)
        .then(results => {
          setPredictions(results.predictions);
        })
        .catch(e => console.warn(e));
    }, 1000),
    [],
  );

  return (
    <Modal
        ...
    </Modal>
  );
}
```
We first import `useState`, `useEffect` and `useCallback` from React. We also import `debounce` from `lodash` and the recent created function utility `APIPlaceAutocomplete`.

We also receive a new prop, `currentPlace`. This prop we could inject into the Modal component using the custom hook from PlacesManager, but I decided to receive it from BookingInformation.

Using useState, we create a local state called `predictions`, which is an empty array, here we will display the list of predictions from Google Places API.

```js
useEffect(() => {
    if (newAddress) {
      debounceSearch(newAddress);
    } else {
      setPredictions([]);
    }
  }, [newAddress, debounceSearch]);
```
If we have a `newAddress`, we call the function `debounceSearch` with the `newAddress`. Otherwise, we call setPredictions with an empty array.

```js
const debounceSearch = useCallback(
    debounce(address => {
      APIPlaceAutocomplete(address, currentPlace)
        .then(results => {
          setPredictions(results.predictions);
        })
        .catch(e => console.warn(e));
    }, 1000),
    [],
  );
```

We use useCallback with debounce, which means that every 1 second, we will call the `APIPlaceAutocomplete` function, passing the two parameters the function needs.

So, let's pass `currentPlace` from BookingInformation as prop to SearchAddressModal component.

```js
...
export default function BookingInformation() {
...

  return (
    <>
      ...

      <SearchAddressModal
        isModalVisible={isModalVisible}
        toggleModal={togglePlaceModal}
        newAddress={newAddress}
        setNewAddress={setNewAddress}
   +    currentPlace={currentPlace}
      />
    </>
  );
}
```

#### Render List of Predictions with Flatlist

We need to render the list of predictions that we stored inside the `predictions` local state. First, let's create a new component inside `src/components/Prediction.js`:

```js
import React from 'react';
import {TouchableOpacity} from 'react-native';
import styled from 'styled-components/native';

const Text = styled.Text`
  padding: 5px;
  font-size: 14px;
`;

export default function Prediction({description, place_id}) {
  return (
    <TouchableOpacity
      key={place_id}
      testID={`prediction-row-${place_id}`}
      onPress={() => {}}>
      <Text>{description}</Text>
    </TouchableOpacity>
  );
}
```

And now let's move into SearchAddressModal and start using it along with Flatlist component from React Native.

```js
...
+ import Prediction from './Prediction';

...

const Predictions = styled.View`
  margin-bottom: 20px;
`;

export default function SearchAddressModal({
  isModalVisible,
  toggleModal,
  newAddress,
  setNewAddress,
  currentPlace,
}) {
  const [predictions, setPredictions] = useState([]);

  useEffect(() => {
    if (newAddress) {
      debounceSearch(newAddress);
    } else {
      setPredictions([]);
    }
  }, [newAddress, debounceSearch]);

  const debounceSearch = useCallback(
    debounce(address => {
      APIPlaceAutocomplete(address, currentPlace)
        .then(results => {
          setPredictions(results.predictions);
        })
        .catch(e => console.warn(e));
    }, 1000),
    [currentPlace, setPredictions],
  );

+  const renderPredictions = ({item}) => <Prediction {...item} />;

  return (
    <Modal
      ...

        <ModalChildrenView>
          ...
        </ModalChildrenView>
+       <Predictions>
          {predictions.length > 0 && (
            <FlatList
              data={predictions}
              renderItem={renderPredictions}
              keyExtractor={item => item.place_id}
            />
          )}
        </Predictions>
      </Container>
    </Modal>
  );
}
```
![Flatlist with predictions](https://media.giphy.com/media/Je0Tor1TtBDgDfL1fb/giphy.gif)

## Uni Tests

It's test time! 😍

We need to add a new tests file for BookingInformation component. On the first test, we will test that the component renders correctly.

Create a new test file `src/components/__tests__/BookingInformation.test.js`:

```js
import React from 'react';
import {render} from '@testing-library/react-native';
import BookingInformation from '../BookingInformation';
import {PlaceContext} from '../../context/PlacesManager';

describe('<BookingInformation />', () => {
  test('should render correctly when not selected destination', () => {
    const place = {
      currentPlace: {
        description: 'Keillers Park',
        placeId: 'abc',
      },
      destinationPlace: {description: '', placeId: ''},
    };
    const dispatchPlace = jest.fn();
    const {getByTestId, getByText} = render(
      <PlaceContext.Provider value={{place, dispatchPlace}}>
        <BookingInformation />
      </PlaceContext.Provider>,
    );

    expect(getByText('Keillers Park')).toBeDefined();
    expect(getByText('Add destination')).toBeDefined();
    expect(getByTestId('destination-label')).toBeDefined();
  });
});
```

Also let's add another test file for the *SearchAddressModal* component. Create a new file `src/components/__tests__/SearchAddressModal.test.js`

```js
import React from 'react';
import {render} from '@testing-library/react-native';
import SearchAddressModal from '../SearchAddressModal';

describe('<SearchAddressModal />', () => {
  test('should render correctly', () => {
    const {getByPlaceholderText, getByTestId, queryByText} = render(
      <SearchAddressModal isModalVisible={true} />,
    );

    expect(getByTestId('back-button')).toBeDefined();
    expect(getByPlaceholderText('Add destination')).toBeDefined();
    expect(getByTestId('clear-button')).toBeDefined();
    expect(queryByText(/Recent/i)).toBeDefined();
  });
});
```
For now, we only test if the component renders correctly. We check for the back button, Add destination placeholder, clear-button, and the Recent places heading.

Now, let's add another test case about 'should render a list of predictions'.

There're a couple of things that we need to test like `lodash.debounce` code and the fetching for APIPlaceAutocomplete function.

Open `src/components/__tests__/SearchAddressModal.test.js`:

```js
test('should render a list of predictions', async () => {
    const lodash = require('lodash');
    lodash.debounce = jest.fn(fn => fn);
    jest.useFakeTimers();
    const promise = Promise.resolve();
    const mockSetNewAddress = jest.fn(() => promise);
    const newAddress = 'Domkyrkan';
    const mockCurrentPlace = {
      description: 'Keillers Park',
      placeId: 'abc',
      latitude: 57.7,
      longitude: 11.93,
    };

    render(
      <SearchAddressModal
        newAddress={newAddress}
        setNewAddress={mockSetNewAddress}
        currentPlace={mockCurrentPlace}
      />,
    );
  });
```

For now, we require `lodash` and mock lodash.debounce function with jest.fn. We need to use jest fake timers to simulate the waiting time.

When we run the test it will fail because we need to mock fetch function we use inside APIPlaceAutocomplete.

For mocking Fetch we use a new library **jest-fetch-mock**. Open your terminal and install [jest-fetch-mock](https://www.npmjs.com/package/jest-fetch-mock).

```js
npm install --save-dev jest-fetch-mock
```

Finish the setup by opening our **jest-setup.js** configuration file and paste the next line 

```js
require('jest-fetch-mock').enableMocks();
```
Also update the test adding a mock response for fetch.

```js
test('should render a list of predictions', () => {
    const lodash = require('lodash');
    lodash.debounce = jest.fn(fn => fn);
    jest.useFakeTimers();

    const results = {
      predictions: [
        {
          description: 'Domkyrkan',
          place_id: '123',
        },
      ],
    };
    fetch.mockResponseOnce(JSON.stringify(results));

    const promise = Promise.resolve();
    const mockSetNewAddress = jest.fn(() => promise);
    const newAddress = 'Domkyrkan';
    const mockCurrentPlace = {
      description: 'Keillers Park',
      placeId: 'abc',
      latitude: 57.7,
      longitude: 11.93,
    };

    render(
      <SearchAddressModal
        newAddress={newAddress}
        setNewAddress={mockSetNewAddress}
        currentPlace={mockCurrentPlace}
      />,
    );
  });
```

We mock the fetch by passing an array of predictions. Now, let's fire and event simulating the user typing a new address:

```js
test('should render a list of predictions', () => {
    const lodash = require('lodash');
    lodash.debounce = jest.fn(fn => fn);
    jest.useFakeTimers();
    const results = {
      predictions: [
        {
          description: 'Domkyrkan',
          place_id: '123',
        },
      ],
    };
    fetch.mockResponseOnce(JSON.stringify(results));
    const promise = Promise.resolve();
    const mockSetNewAddress = jest.fn(() => promise);
    const newAddress = 'Domkyrkan';
    const mockCurrentPlace = {
      description: 'Keillers Park',
      placeId: 'abc',
      latitude: 57.7,
      longitude: 11.93,
    };

    const {getByPlaceholderText} = render(
      <SearchAddressModal
        newAddress={newAddress}
        setNewAddress={mockSetNewAddress}
        currentPlace={mockCurrentPlace}
      />,
    );

    fireEvent.changeText(getByPlaceholderText('Add destination'), newAddress);
    expect(mockSetNewAddress).toHaveBeenCalledWith(newAddress);
  });
```
We select **getByPlaceholderText** function from the render component and import **fireEvent** in order to add a new address. Then we assert that the local state, that update the InputText have been called.

Let's finish by adding the rest about `loadash.bounce` and rendering a list of predictions.

```js
test('should render a list of predictions', async () => {
    const lodash = require('lodash');
    lodash.debounce = jest.fn((fn) => fn);
    const onResponse = jest.fn();
    const onError = jest.fn();
    jest.useFakeTimers();
    const results = {
      predictions: [
        {
          description: 'Domkyrkan',
          place_id: '123',
        },
      ],
    };
    fetch.mockResponseOnce(JSON.stringify(results));
    const promise = Promise.resolve();
    const mockSetNewAddress = jest.fn(() => promise);
    const newAddress = 'Domkyrkan';
    const mockCurrentPlace = {
      description: 'Keillers Park',
      placeId: 'abc',
      latitude: 57.7,
      longitude: 11.93,
    };

    const {getByPlaceholderText, queryByTestId} = render(
      <SearchAddressModal
        newAddress={newAddress}
        setNewAddress={mockSetNewAddress}
        currentPlace={mockCurrentPlace}
        currentSession={currentSession}
        dispatchAuth={dispatchAuth}
      />,
    );

    fireEvent.changeText(getByPlaceholderText('Add destination'), newAddress);
    expect(mockSetNewAddress).toHaveBeenCalledWith(newAddress);

    lodash.debounce(
      APIPlaceAutocomplete(newAddress, mockCurrentPlace)
        .then(onResponse)
        .catch(onError)
        .finally(() => {
          expect(onResponse).toHaveBeenCalled();
          expect(onError).not.toHaveBeenCalled();

          expect(onResponse.mock.calls[0][0][0]).toEqual(results);
        }),
      1000,
    );

    expect(queryByTestId('prediction-row-0')).toBeNull();

    await act(() => promise);
    queryByTestId('prediction-row-0');
  });
```

Don't forget to import APIPlaceAutocomplete `import {APIPlaceAutocomplete} from '../../utils';` and **act** function from testing-library.

See how we `async/await` for a promise to be resolved so we can see a list of predictions using `await act(() => promise);`


🛑 Stop!

That's it from me for now. I hope you're doing good by now and learning a thing or two. We'll continue working on the Booking Information component in the next post.

Stay tuned!