React Instantsearch, Redux, and Nextjs

Hello! I have a pretty big one here. There are a lot of moving parts: Algolia, Redux, Next.js. I understand that some of these may be Next.js issues, and not Algolia issues, and so you guys may not be able to help with those. That’s okay, but of course any help is appreciated!

I used this template provided by you guys to build my example:

Quick overview of the changes I made:

  • I changed the main Index page to a dynamic page: /search/[slug]
  • I changed it from a React class component to a functional component
  • I implemented some Redux boilerplate from this Next.js example

My goal: Connect Algolia widgets across multiple components.

  • As an example of this, I put in a <Stats /> widget in head.js
  • Since the website will contain multiple pages and not only the search page, I do not think it is a good idea to wrap the <InstantSearch /> component around the whole website, as most pages would not use it.

Algolia Problems:

  • The <Stats /> widget does not show at first: it only appears after a refinement is set.
  • The <Stats /> widget never updates: it only shows the total hits in the index.
    • I thought that if I just provided the searchState object to the <InstantSearch /> object in head.js, that it would update the widget accordingly.
    • If you check the console, you can see that it logs the searchState changes from Redux correctly (or, it seems to…)

Not sure if Algolia or Next.js problems:

  • getInitialProps is hit twice on refresh.
    • To reproduce: go to search page => add refinement => refresh and then read the Terminal messages. You will see “~~ Initial State” console.log message appear correctly, and then immediately after appear empty. I believe this is why refinements are not set from URL: it sets them correctly, but then sets them to blank.
  • Potentially related to the above, but the main page seems to render multiple times: add a refinement and you will see that the searchState updates twice (logged in the console).
    • I think this may be unrelated because both times, the searchState is correct, whereas with the previous issue the second render has an incorrect searchState.
  • Not implemented in the example, but if I add a onSearchStateChange function to the <InstantSearch /> in head.js which calls Redux dispatch(), I get an infinite loop. This means I cannot add, for example, a <SortBy /> to head.js.

Hi there,
Thanks for reaching out to us.
Since you have a bunch of questions, let’s tackle one by one.
For now, I will address only one problem first.
After we solve it, let’s move forward.

I see you wrapped Stats with InstantSearch in head.js and
also wrapped all the InstantSearch-related components with InstantSearch in app.js.

Having multiple InstantSearch components won’t automatically let them communicate with each other. To show you what happened to Stats in head.js, I put Stats to both head.js and app.js.

Kapture 2020-03-17 at 15.36.44

Two InstantSearch components are independent. Stats in head.js is showing nothing at first is because it doesn’t trigger any API call with only InstantSearch and Stats component.

SearchBox component in app.js triggers the first API call with empty query. Then next time Stats in head.js is rendered, it will refer to the browser cache. So it keeps displaying 21,469 no matter what you do with the refinement list in app.js.

To make components work smoothly in the lifecycle of InstantSearch, it’d be better to wrap all of them with InstantSearch component. Or, if you will just display some stats on head.js, then you could wrap Head and App with React Context and you pass searchResults to Head via React Context.

Does this make sense to you?
Let us know if this solves a part of you issues and let us know what your next question might be.

1 Like

Thank you for the response, it is appreciated!

Having multiple InstantSearch components won’t automatically let them communicate with each other.

Yes, I thought that having both InstantSearch components have the same searchState would form the connection that they need.

Or, if you will just display some stats on head.js, then you could wrap Head and App with React Context and you pass searchResults to Head via React Context.

However, I guess it seems that I should have kept searchResults synced between the two instead? When you say this, do you mean the resultsState object that is being passed into <InstantSearch> in search/[slug].js?

To make components work smoothly in the lifecycle of InstantSearch, it’d be better to wrap all of them with InstantSearch component.

To be clear, this is not possible, correct?

<InstantSearch>
  <Head />
  <App />
</InstantSearch>
{/* TypeError: this.props.contextValue.store.getState is not a function

So your suggestion, to my understanding, is that I should wrap both the App and the Head in React Context like this:

// search/[slug].js
<AlgoliaContext.Provider>
  <Head />
  <App />
</AlgoliaContext.Provider>

The React Context would provide the <InstantSearch> components in both <Head> and <App> the resultsState object. The <Head> would recieve the resultsState from the Context and use it to update <Stats> accordingly, and <App> would send updates to the resultsState Context when changes are made.

If I’ve got that right, then my questions are:

  1. Do the <InstantSearch> components require searchState in addition to lastResults?
  2. There is already a Redux store set for searchState; why introduce Context as well? Would it not make more sense to just add resultsState to the Redux store as well?
  3. (Perhaps to be dealt with after) If I wanted <Head> to not only provide <Stats/> but also a <SortBy/> component, do I need resultsState too or is searchState enough?

Sorry, Redux is still new to me and my understanding of React Context is pretty spotty as well.

This should be the correct way to go, since InstantSearch is the context provider. It’s surprising that you see an error like that, it implies that you have another algolia component outside of the tree.

Can you try to recreate this in code sandbox please? The sandbox you made earlier seems to be something different. In that sandbox I see you make more contexts. This normally shouldn’t be necessary, since you can use custom widgets (using connectXXX) to get access to all variables from InstantSearch, as long as they are underneath InstantSearch in the tree

Thanks

1 Like

Here is the recreated version:

I can’t find any other instances of the <InstantSearch> component other than the one in search/[slug].js. All I did here was remove the InstantSearch component from both App and head, and moved it out to [slug] to wrap them both.

In that sandbox I see you make more contexts.

Oops. I messed around with Context a little and I guess I forgot to remove a line or two. It isn’t actually used anywhere.

EDIT:
Okay, figured it out. I’ve reconstructed my App’s Search Page to be more like the one in the sandbox, so that the <InstantSearch> component can wrap everything. This way, I can have <Stats> and <Sort> and whatever else in a separate component (e.g. head.js). Thank you!

[Removed other issues]

I hate to ask for help so often, but I can’t seem to get figure it out on my own. :disappointed:

So nothing has been changed since the last post: the InstantSearch component was moved out of App and into the search/[slug] page in order to wrap both the Header and the App. This means that the Stats component in the Header now properly updates, as you can see above. Yay!

Unfortunately, it seems that moving InstantSearch has broken SSR. Configure was changed so that you can see more clearly: a filter was added and I changed hitsPerPage to 2. You can see that if you go to the search page and refresh, you will see SSR load ~20k results, and then client-side kicks in an shows only 256 as required.

getResultsState does not seem to see the Configure object. Modifying or returning a different searchState (line 58 in search/[slug]) does not seem to make a difference.

Changing the component passed into getResultsState to Page throws Redux errors, and I see no other viable options to pass into it.