react-instantsearch icon indicating copy to clipboard operation
react-instantsearch copied to clipboard

Instantsearch server-side rendering is also fetching on the client

Open francisrod01 opened this issue 4 years ago • 16 comments

Describe the bug 🐛

Maybe is because the mock request I'm doing for empty query. I don't know the cause.

To Reproduce 🔍

  1. Go to https://sgk4r.sse.codesandbox.io/?query=amazon&page=1&configure%5BhitsPerPage%5D=4
  2. Open the Chrome console and refresh the page
  3. Go to Network tab and see the queries request made on the client

A live example helps a lot! :100:

Your application on codesandbox does not seem to include the code from the guide, are you saying it's solved or not? thanks Originally posted by @Haroenv in https://github.com/algolia/react-instantsearch/issues/2960#issuecomment-671830911

Here's a codesandbox: https://codesandbox.io/s/nextjs-algolia-instantsearch-sgk4r

Expected behavior 💭

A clean rendering ONLY on the server-side and see the HTML appropriately on the page.

Screenshots 🖥

image

Environment:

  • OS: Linux debian 4.9.0-13-amd64
  • Google Chrome Browser Version 84.0.4147.125 (Official Build) (64-bit)
  • NPM: 6.14.4
  • Node.js: 10.20.1
  • next: 9.3.5
  • react: 16.13.1
  • algoliasearch: 4.2.0
  • react-instantsearch-dom: 6.6.0

Additional context

francisrod01 avatar Aug 11 '20 14:08 francisrod01

Hmm, I originally thought that this could be due to the fact that the custom client for conditional requests no longer has the cache functionality, and can't be hydrated. If that was the case, the solution would be:

const searchClientSearch = searchClient.search;
searchClient.search = function search(requests) {
  if (requests.every(({ params }) => !params.query)) {
    return Promise.resolve({
      results: requests.map(() => ({
        hits: [],
        nbHits: 0,
        nbPages: 0,
        processingTimeMS: 0
      }))
    });
  }

  return searchClientSearch(requests);
};

as in https://codesandbox.io/s/nextjs-algolia-instantsearch-forked-wh7cu?file=/modules/instantsearch/services.js

Unfortunately that does not seem to be the cause, so further investigation is needed why the cache isn't hit 🤔

Haroenv avatar Aug 13 '20 14:08 Haroenv

I can see it search on server-side the first time, than in a second or two, starts fetching on queries?x-algolia-agent=..... two times on the client.

I look forward to see it fixed. :100:

francisrod01 avatar Aug 13 '20 22:08 francisrod01

This does not seem to be an issue when using getInitialProps (as used in the example here). Is there any chance this is related to this issue?

callmephilip avatar Oct 27 '20 19:10 callmephilip

Do you also have an issue @callmephilip? Would love to see another (more minimal) example of what could be going on

Haroenv avatar Oct 28 '20 09:10 Haroenv

@Haroenv i have a reproduction here

  • basically it boils down to wrapping a helper component in connectStats (we were using this to adjust how filters are rendered based on the number of search results + for adjusting some UI elements)

callmephilip avatar Nov 09 '20 17:11 callmephilip

Ah yes that makes sense, the stats will not render on the server, since there's no results yet, and thus it will not render the refinements either. Since it doesn't render, the widgets will not be taken in account for server side rendering, and the state won't be the same on the client as on the server.

An easy workaround is to run findResultsState twice, which is an extra search on the server, or alternatively I think it might be possible to work something out where you pass resultsState to findResultsState already with a basic state that will cause stats to render.

Thanks for the reproduction @callmephilip, let my know if my suggestions make sense / work!

Haroenv avatar Nov 10 '20 08:11 Haroenv

@Haroenv i don't think i follow completely.

looking at this here

static async getInitialProps({ asPath }) {
    const searchState = pathToSearchState(asPath);
    const resultsState = await findResultsState(App, {
      ...DEFAULT_PROPS,
      searchState,
    });

    return {
      resultsState,
      searchState,
    };
}

resultsState should already contain all the information necessary to pull stats for the search. here's a snapshot from the server log:

{ metadata:
   [ { id: 'query', index: 'instant_search', items: [Array] },
     { id: 'categories', index: 'instant_search', items: [] },
     { id: 'page' } ],
  rawResults:
   [ { hits: [Array],
       nbHits: 21469,
       page: 0,
       nbPages: 84,
       hitsPerPage: 12,
       facets: [Object],
       exhaustiveFacetsCount: true,
       exhaustiveNbHits: true,
       query: '',
       queryAfterRemoval: '',
       params:
        'highlightPreTag=%3Cais-highlight-0000000000%3E&highlightPostTag=%3C%2Fais-highlight-0000000000%3E&hitsPerPage=12&query=&maxValuesPerFacet=10&page=0&facets=%5B%22categories%22%5D&tagFilters=',
       index: 'instant_search',
       processingTimeMS: 3 } ],
  state:
   SearchParameters {
     facets: [],
     disjunctiveFacets: [ 'categories' ],
     hierarchicalFacets: [],
     facetsRefinements: {},
     facetsExcludes: {},
     disjunctiveFacetsRefinements: {},
     numericRefinements: {},
     tagRefinements: [],
     hierarchicalFacetsRefinements: {},
     index: 'instant_search',
     highlightPreTag: '<ais-highlight-0000000000>',
     highlightPostTag: '</ais-highlight-0000000000>',
     hitsPerPage: 12,
     query: '',
     maxValuesPerFacet: 10,
     page: 0 } }

callmephilip avatar Nov 10 '20 13:11 callmephilip

@Haroenv any updates on this?

callmephilip avatar Dec 10 '20 16:12 callmephilip

Hi @callmephilip, sorry, I missed your response! I think nested connectors in this way basically don't render their children if no results have yet been set, since the flow is:

  1. findResultsState
  2. internal render to find all components
  3. stats doesn't render its children, since there's not yet results
  4. children of stats don't render
  5. search happens without stats' children
  6. main render happens

A solution could be making a custom version of connectStats which does render if there's no results

Haroenv avatar Dec 16 '20 16:12 Haroenv

Hi @callmephilip, sorry, I missed your response! I think nested connectors in this way basically don't render their children if no results have yet been set, since the flow is:

1. findResultsState

2. internal render to find all components

3. stats doesn't render its children, since there's not yet results

4. children of stats don't render

5. search happens without stats' children

6. main render happens

A solution could be making a custom version of connectStats which does render if there's no results

Stumbled upon this issue too: https://codesandbox.io/s/flamboyant-sanderson-kx6ns?file=/pages/search.js

MANTENN avatar Mar 27 '21 10:03 MANTENN

Hi @callmephilip, sorry, I missed your response! I think nested connectors in this way basically don't render their children if no results have yet been set, since the flow is:

1. findResultsState

2. internal render to find all components

3. stats doesn't render its children, since there's not yet results

4. children of stats don't render

5. search happens without stats' children

6. main render happens

A solution could be making a custom version of connectStats which does render if there's no results

Error: Error serializing `.initialResultsState.metadata[0].items[0].value` returned from `getServerSideProps` in "/search". Reason: `function` cannot be serialized as JSON. Please only return JSON serializable data types.

It's undefined so it can't be parsed.

MANTENN avatar Mar 27 '21 10:03 MANTENN

So I did a production build and the build completed without any errors. I also navigated on the page and it works too. It's broken in development only.

MANTENN avatar Mar 27 '21 10:03 MANTENN

What's the point of passing the component into the getResultsState function.

MANTENN avatar Mar 27 '21 21:03 MANTENN

I narrowed it down to:

const singleIndexSearch = (helper, parameters) => helper.searchOnce(parameters).then((res) => ({
      rawResults: cleanRawResults(res.content._rawResults),
      state: res.content._state,
    }))

MANTENN avatar Mar 27 '21 21:03 MANTENN

I'm guessing ServerSideProps works differently than getInitialProps. The first render with ServerSideProps is resultsState is undefined therefore it errors out. I just added an initialState to resultsState and it works.

Demo: https://codesandbox.io/s/practical-frog-y0thh?file=/pages/index.js

MANTENN avatar Mar 27 '21 22:03 MANTENN

Code

Search.js file

  const resultsState = initialResultsState || {
    metadata: [
      {
        id: "",
        index: "",
        items: [
          {
            label: "",
            currentRefinement: "",
          },
        ],
      },
      {
        id: "",
      },
    ],
    rawResults: [
      {
        index: "",
        hitsPerPage: 0,
        exhaustiveNbHits: false,
        nbHits: 0,
        processingTimeMS: 0,
        query: "",
        nbPages: 0,
        page: 0,
        hits: [],
      },
    ],
    state: {},
  };
  
  
  return <InstantSearch
            searchClient={searchClient}
            resultsState={resultsState}
            onSearchStateChange={onSearchStateChange}
            searchState={searchState}
            createURL={createURL}
            {...DEFAULT_PROPS}
            widgetsCollector={widgetsCollector}
            {...resProps}
          >

MANTENN avatar Mar 27 '21 22:03 MANTENN

Hi! As React InstantSearch Hooks does not have this behavior, we decided not to add new features/fixes to this feature in React InstantSearch. Since this issue seems not to have generated much activity lately, so we're going to close it, feel free to reopen if needed.

dhayab avatar Dec 22 '22 10:12 dhayab