Search UI Dynamically Change Facets Search parameters From External Component

Hi,

I am trying to dynamically modify the geo center in the range search for the National Parks codesandbox example.
I am trying to changing the geo-center coordinate for the range search via custom component with select menu as described in Build Your Own Component.

Here is the forked National Parks example with the implementation so far.

The custom component in the ChangeGeocenter.js.

import React from "react";
import Select from "react-select";

import { withSearch } from "@elastic/react-search-ui";

const options = [
  { value: "40.7128, -74.0060", label: "New York" },
  { value: "41.8781, -87.6298", label: "Chicago" },
  { value: "37.7749, -122.4194", label: "San Francisco" }
];

class ChangeGeoCenter extends React.Component {
  state = {
    selectedOption: null
  };
  handleChange = selectedOption => {
    this.setState({ selectedOption });
    console.log(`Option selected:`, selectedOption);
  };
  render() {
    const { selectedOption } = this.state;

    return (
      <fieldset className="sui-facet">
        <legend className="sui-facet__title">City Center</legend>
        <Select
          value={selectedOption}
          onChange={this.handleChange}
          options={options}
        />
      </fieldset>
    );
  }
}

export default withSearch(({ ChangeGeoCenter }) => ({
  ChangeGeoCenter
}))(ChangeGeoCenter);

How can I dynamically override the center parameter of the location range facets using the custom component above?

 location: {
        // Overwrite the center value from the select menu?
        center: "37.7749, -122.4194",
        type: "range",
        unit: "mi",
        ranges: [
          { from: 0, to: 100, name: "Nearby" },
          { from: 100, to: 500, name: "A longer drive" },
          { from: 500, name: "Perhaps fly?" }
        ]
      },
1 Like

You should probably lift the selectedOption state out of that component up to App.js, and use the selecteOption to populate the center property of the location facet, here.

You'll need the 1.4.0 RC version in order for this to work: https://github.com/elastic/search-ui/releases/tag/v1.4.0-rc.0

Dynamic facet support was just recently added: https://github.com/elastic/search-ui/pull/471

Does that make sense?

This is probably a react newbie question but, how do I transfer the state from selectOption to geocenter ?

What I did so far

  1. Updated all the libraries to the latest versions.
    https://codesandbox.io/s/search-ui-national-parks-example-blfll

  2. I initialized the geocenter

 const geocenter = { value: "37.7749, -122.4194" };

My location facet becomes

     location: {
          // San Francisco. In the future, make this the user's current position
          center: geocenter.value,
          type: "range",
          unit: "mi",
          ranges: [
            { from: 0, to: 100, name: "Nearby" },
            { from: 100, to: 500, name: "A longer drive" },
            { from: 500, name: "Perhaps fly?" }
          ]
        }
  1. Moved the config in the app
export default function App() {
  const geocenter = { value: "37.7749, -122.4194" };
  const config = { ...
... 
  1. In the ChangeGeocenter attempted to set the geocenter via this.setState
class ChangeGeocenter extends React.Component {
  state = {
    selectedOption: null
  };
  handleChange = selectedOption => {
    this.setState({ selectedOption });
    this.setState({ geocenter: selectedOption });
    console.log(`Option selected:`, selectedOption);
  };

You'd basically need to lift the state up to be stored as state in App.js. You'd pass the current value of geocenter into ChangeGeoCenter, as well as a callback to update it in App.js. React has some great docs on lifting state: https://reactjs.org/docs/lifting-state-up.html.

Let me know if you are still confused after react that and I'll try to put together an example.

Thank you Jason,

Almost there. What I did so far.

  1. Converted the App function to React Class Component and initialized the geocenter in the constructor.
// export default function App() {
class App extends React.Component {
  constructor(props) {
    super(props);
    this.handleGeocenterChange = this.handleGeocenterChange.bind(this);
    this.state = {
      geocenter: { value: "37.7749, -122.4194", label: "San Francisco" }
    };
  }
  handleGeocenterChange(selectedOption) {
    this.setState({ geocenter: selectedOption });
  }
  render() {
    const geocenter = this.state.geocenter;
    const config = {....

  1. I am able to lift the state from ChangeGeoCenter via this.props.onGeocenterChange
  handleChange(selectedOption) {
    this.setState({ selectedOption });
    this.props.onGeocenterChange(selectedOption);
    console.log(`Option selected:`, selectedOption);
  }

to the parent class.

<ChangeGeocenter
       onGeocenterChange={this.handleGeocenterChange}
 />

When I make the change in the select I can see the updates of the the values in the facet above.
However this does not update the search results. I would need to re-check the Distance filter in order to refresh the results.
Screenshot_20200506_023841

Can you help me with this final part.

Miroslav

Great! And yeah, there's no super elegant solution to re-query right now, so do this...

You still have WithSearch in there, right? That lets you access underlying state and actions. Just call setSearchTerm with the current resultSearchTerm to rerun the current query.

<WithSearch mapContextToProps={({ setSearchTerm,  resultSearchTerm }) => ({ setSearchTerm,  resultSearchTerm })}>
    {({ setSearchTerm,  resultSearchTerm }) => (
       ...
         setSearchTerm(resultSearchTerm)
       ...
    )}
  </WithSearch>

Since it looks like you need to call that in your handleGeocenterChange handler, you may need to use the withSearch Higher Order Component instead of WithSearch. The pattern is the same, you just add it slightly differently...

import React from "react";
import { withSearch } from "@elastic/react-search-ui";

class App extends React.Component {
    handleGeocenterChange(selectedOption) {
      const { setSearchTerm, resultSearchTerm } = this.props;
      this.setState({ geocenter: selectedOption }, () => {
        setSearchTerm(resultSearchTerm);
      });
    }
}

export default withSearch(({ setSearchTerm, resultSearchTerm }) => ({
  setSearchTerm,
  resultSearchTerm
}))(App);

Not sure how to set the setSearchTerm(resultSearchTerm)

    return (
      <SearchProvider config={config}>
        <WithSearch mapContextToProps={({ wasSearched,setSearchTerm,  resultSearchTerm  }) => ({ wasSearched,setSearchTerm,  resultSearchTerm })}>
          {({ wasSearched,setSearchTerm,  resultSearchTerm }) => {
            return (
                <div className="App"> ...

I don't know, try something like this maybe?

<ChangeGeocenter
       onGeocenterChange={(...params) => { this.handleGeocenterChange(setSearchTerm, resultSearchTerm, ...params) }
 />

Then update your handleGeocenterChange to call setSearchTerm?

@M_M I adapted your example so that it is now working: https://codesandbox.io/s/search-ui-dynamic-facets-example-fzq1y.

The only thing that is not so great about this, is when we call setSearchTerm is actually erases your previous filters. I'm guessing this is not what you are looking for.

I could add some sort of work around for this faily quickly to a 1.4.0-rc.1 if that helps.

1 Like

Thank you Jason,
Erasing the previous filters is a minor inconvenience for my use-case. For me it was more important to preserve the selected set of filters via

 <SearchBox
        shouldClearFilters={false}
        ...
 />
1 Like

Ah, right. I completely forgot about that option.

In that case, changing to this seems to work:

   this.setState({ geocenter: selectedOption }, () => {
      setSearchTerm(resultSearchTerm, { shouldClearFilters: false });
      removeFilter("location");
    });

We have to remove the filter from location, because unfortunately it appears that there is a bug where if you change the City Center and a filter had been previously applied to Distance, that the OLD value for "nearby", etc will be retained.

In other words, if you were looking at "nearby" values for "Chicago", and change to "San Franciso", it would still be showing "nearby" values for "Chicago".

For now, the solution is to just clear the location filter. With some somewhat tricky logic we could make this work, if we need to.

I've update the example.

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.