Newbie React Component Questions

Hi new friends! I’m very new to Algolia, so please be gentle…

I’m testing out Algolia react components for a Gatsby-based documentation site. I have the <InstantSearch> component working in my header, complete with a searchresult overlay, following the guide here.

I’d like to also make a standard search result page for our product to be able to link to, which accepts query params and displays in a separate results field outside of the header. I’ve got the query params inserted into the search component in the header, but I’m unsure how to get a results component that lives in a separate file take the results from that query.

If that’s not possible and I need to construct a new <InstantSearch> component from scratch on that page, then do I need to disable the one in the header? I’ve tried just having two, but the in-page components don’t seem to use the query.

Thanks in advance!

I may be rubber ducking a bit here, so please forgive me. I’ve set up the search component in my header to not render on the search page, and imported the search component again in the body of the search page.

I’ve then set up the <StyledSearchResult> component twice. The first is given the primary property header, which I’m using to conditionally render the popover CSS. All that’s working.

My concern now is using searchState to import the query from the URI parameters. When that value is set, the search field is no longer editable. When it’s not set, the search box works, but it doesn’t load the search value from the URI.

Are searchState and onSearchStateChange mutually exclusive? How can I load a page with a default search result, but still allow new searches?

edit: Here’s the code:

export default function Search({ indices, page, searchQuery}) {
  //console.log("searchQuery in search component: ", searchQuery) //For Debugging
  const rootRef = createRef()
  const [query, setQuery] = useState(searchQuery)
  const defaultQuery = {query:searchQuery, page: "1"}
  const [hasFocus, setFocus] = useState(false)
  const searchClient = algoliasearch(
    process.env.GATSBY_ALGOLIA_APP_ID,
    process.env.GATSBY_ALGOLIA_SEARCH_KEY
  )
  useClickOutside(rootRef, () => setFocus(false))
  return (
    <ThemeProvider theme={theme}>
      <StyledSearchRoot ref={rootRef}>
        <InstantSearch
          searchClient={searchClient}
          indexName={indices[0].name}
          onSearchStateChange={({ query }) => setQuery(query)}
          searchState={defaultQuery}
        >
          <StyledSearchBox onFocus={() => setFocus(true)} hasFocus={hasFocus}/>
          <StyledSearchResult
            show={query && query.length > 0 && hasFocus && page !== "search"}
            indices={indices}
            header
          />
          <StyledSearchResult
            show={page === "search"}
            indices={indices}
          />
        </InstantSearch>
      </StyledSearchRoot>
    </ThemeProvider>
  )

Hi @alex.fornuto,

The simplest solution would be to have two instances of InstantSearch. They are completely independent from each other. You would have to sync them with an external store e.g. the URL. You can initialize InstantSearch with a searchState to set the initial parameters retrieved from the URL.

You can probably instantiate the client once and expose it through the context. It would have the advantage that both instances will use the same client hence the same cache. Two queries with the same parameters will hit Algolia only once. It’s not a hard requirement though. You can do it in a second step. In Gatsby, the API to scope component at the top of the tree is wrapRootElement.

Hope that helps!

1 Like

@samuel.vaillant thanks for the quick reply, and apologies for updating from the original issue while you were answering it.

My example code in my second post may not be the simplest solution, but it’s working for me, with the exception of the searchbox not being editable when searchState is defined.

They’re not mutually exclusive but once you’re using the “control” mode it’s your responsibility to keep the searchState up to date. In the example you gave the defaultQuery is never updated hence the issue. You can probably merge the two variable to have something like this:

const [searchState, setSearchState] = useState({
  query: searchQuery,
  page: "1",
});

// ...

<InstantSearch
  onSearchStateChange={setSearchState}
  searchState={searchState}
/>

Hope that helps!

1 Like

That does help, thank you!

My next improvement will be to load InstantSearch in both the header and the body of the search page, and retrieve the results from the header into a results component in the body. There’s an old post that alluded to this being possible, but the docs have changed in the years since that link was given, so I’m not sure how to proceed.

For those following along at home, using the Basic URLs documentation worked for me, but with one caveat: Instead of using history.pushState I had to use the navigate function from @reach/router:

    setDebouncedSetState(
      setTimeout(() => {
        navigate(
          null,
          {updatedSearchState},
          searchStateToUrl(updatedSearchState),
        );
      }, DEBOUNCE_TIME)
    );

P.S. Another change required on the line:

const urlToSearchState = ({ search }) => qs.parse(search.slice(1));

{search} is not defined, I had to replace it with location, which itself is defined as var location = window.location.