React Places geosearch with specific radius?

Hey :wave:

I’d like to build a component where the user can select a location (either using Algolia Places or Google Places) and a radius to filter the search results by.

I’ve found the docs for Places+React, but it only specifies how to create a connector that modifies the aroundLatLng value - how would I expand that so I could pass radiusInMeters along to the refine method and new search state?

Thanks!

Hi,

I am not 100% sure I understood the scope of what you are trying to achieve, so would it possible for you to clarify the following point?

Would this component be used as a standalone component, independent of our InstantSearch library, or would it be used as a way to filter the search results of an index managed by InstantSearch?

Thanks!

Hey,

I’m using the InstantSearch library, and would like to refine my search using lat , lng and radiusInMeters . I’ll get the coordinates using a Google Places widget, and the radius from a normal TextField . How do I pass those params to the InstantSearch instance in React? :smile:

Here’s some code:

The connector, heavily inspired from the example in the docs:

import { SearchParameters } from 'algoliasearch-helper';
import { createConnector } from 'react-instantsearch-dom';

export type GeoSearchInput = {
  location?: {
    lat: number;
    lng: number;
  };
  radiusInMeters?: string;
};

export default createConnector({
  displayName: 'AlgoliaPlaces',

  getProvidedProps() {
    return {};
  },

  refine(props, searchState, nextValue: GeoSearchInput) {
    const { location, radiusInMeters } = nextValue;
    const aroundLatLng = location ? `${location.lat},${location.lng}` : null;
    const aroundRadius = radiusInMeters ? radiusInMeters : null;
    console.log(`Refine in places refinement called`);

    return {
      ...searchState,
      aroundLatLng,
      aroundRadius,
      boundingBox: {},
    };
  },

  getSearchParameters({ aroundLatLng, aroundRadius }, props, searchState) {
    return new SearchParameters({ aroundLatLng, aroundRadius });
  },
});

And here’s the component that gets the user input and passes the info the the refine function:

import Grid from '@material-ui/core/Grid/Grid';
import TextField from '@material-ui/core/TextField/TextField';
import React, { useEffect, useState } from 'react';
import { ConnectorProvided } from 'react-instantsearch-core';

import { GoogleMapsApiHOC, WithGoogleMapsApi } from '../../../../../common/google-maps-api/GoogleMapsApi';
import PlacesAutocompleteInput from '../../../../../common/input/PlacesAutocompleteInput';
import Loading from '../../../../../common/Loading';
import connect, { GeoSearchInput } from './ARPlacesRefinementConnector';

interface Props {}

type PropsType = Props & WithGoogleMapsApi & ConnectorProvided<{}>;

const PlacesRefinement: React.FC<PropsType> = ({ getGeoCodeFromPlaceID, refine }) => {
  const [geoSearchState, setGeoSearchState] = useState<GeoSearchInput>({});
  useEffect(() => {
    if (geoSearchState.location && geoSearchState.radiusInMeters) {
      console.log(`Refining with ${JSON.stringify(geoSearchState)}`);
      refine(geoSearchState);
    }
  }, [geoSearchState, refine]);

  if (!getGeoCodeFromPlaceID) {
    return <Loading />;
  }

  const refineSearch = async (inputValue: string) => {
    const inputValueReference = inputValue;
    const geoLocation = await getGeoCodeFromPlaceID(inputValueReference);

    if (geoLocation && geoLocation[0] && geoLocation[0].address_components) {
      const lat = geoLocation[0].geometry.location.lat();
      const lng = geoLocation[0].geometry.location.lng();

      console.log(`lat ${lat} lng ${lng}`);

      setGeoSearchState({
        ...geoSearchState,
        location: {
          lat,
          lng,
        },
      });
    }
  };
  return (
    <Grid container={true}>
      <PlacesAutocompleteInput onChange={refineSearch} />
      <TextField
        label={'Radius in KM'}
        type="number"
        onChange={event =>
          setGeoSearchState({
            ...geoSearchState,
            radiusInMeters: event.currentTarget.value ? Math.floor(Number(event.currentTarget.value) * 1000) + '' : '',
          })
        }
      />
    </Grid>
  );
};

export const ARPlacesRefinement = GoogleMapsApiHOC(connect(PlacesRefinement));

I’m passing aroundLatLng and aroundRadius in the correct formats I think?

However, when I include this component in my search from (as a child of the root InstantSearch), I get this error for all queries in the form:

message: "objects must contain params and indexNames attributes."
status: 400

The form data in the request is faulty:

{"requests":[{"params":"facets=%5B%5D&tagFilters="}]}

so it seems my custom connector is screwing up the search state for the whole form.

Here’s the output from the console logs once I’ve chosen a location (Berlin) and a radius:

Refining with {"location":{"lat":52.52000659999999,"lng":13.404953999999975},"radiusInMeters":"200000"}

Refine in places refinement called

What am I doing wrong? :smile:

Hi there,
thanks for coming up with the sample code.
It really helps a lot.

The object returned from refine is the same with searchState at getSearchParameters.

Your getSearchParameters is the following:

  getSearchParameters({ aroundLatLng, aroundRadius }, props, searchState) {
    return new SearchParameters({ aroundLatLng, aroundRadius });
  },

but you can change it to the following:

  getSearchParameters(searchParameters, props, { aroundLatLng, aroundRadius }) {
    return searchParameters.setQueryParameters({
      aroundLatLng,
      aroundRadius,
    });
  },

Previously you were creating a fresh searchParameters which made you lose all the information, but now in this way, you update the searchParameters with aroundXX values.

Just in case you haven’t checked, we have a geo search example for React-InstantSearch: https://codesandbox.io/s/github/algolia/react-instantsearch/tree/master/examples/geo-search

Please let me know how it goes and if you have any questions.
Have a nice day!

Hey @eunjae.lee,

Thanks, it (almost) works! :smile: Silly mistake on my part to new the params.

However, the location filter+radius doesn’t show up on the refinement list

Here, I’ve applied a gender filter along with the location+radius filter, but only the gender tag is shown in the <CurrentRefinements /> widget - why is that?

Hi there,

The reason that the location and radius are not showing up in the <CurrentRefinements /> widget is that these are technically not refinements. A refinement can only be done on a facet, and geo-attributes are not facets: they are merely a way to rank or filter your results. Hope this help!

Cheers,
Devin.

Hi @devin.beeuwkes,

Thanks for helping out. However, it doesn’t make sense to me. I would expect aroundLatLng+radiusInMeters to be a filter , not a ranking/sorting. It lies in the wording of the parameters- if I only passed aroundLatLng, a sorted result around that point would make sense. But when I couple it with radiusInMeters, I expect it to turn into a filter.

It seems that the stats widget takes it into account, as it only shows the number of users inside those bounds?

Hi @jm1,
In general terms, aroundLatLng is a filter which narrows search result.
However, in Algolia, filter means either numeric filters or facet filters.
aroundLatLng and aroundRadius are one of search parameters.
That’s why they’re not displayed on CurrentRefinements.

I’ve made an example to display aroundLatLng and aroundRadius.
In the example, connectStateResults is used to create a custom widget.

Here is the sandbox. Please check CurrentGeo component in App.js.

Please let me know how it goes and if you have any question.
Have a nice week!

1 Like