question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

infinite loop when rendering a conditional loading message with children

See original GitHub issue

Do you want to request a feature or report a bug?

Bug.

Bug: What is the current behavior?

(See below for full description.)

Attempting to use a connected “loading message” component (one that renders static content when loading and its children when finished) causes an infinite loop in certain circumstances.

  • when children contains the <Stats /> component, searchResults.searching is set as expected.
  • when children contains the <Hits /> and <Pagination /> components (possibly others), searchResults.searching flips back and forth ad infinitum.

When the “loading message” component is configured to render static content upon completion, the infinite loop does not occur.

Bug: What is the expected behavior?

searchResults.searching should not flip-flop between true and false in an infinite loop when rendering children.

Bug: What browsers are impacted? Which versions?

Observed in Chrome 59.0.3071.109 & Safari 10.1.1.

What is the version you are using? Always use the latest one before opening a bug issue.

Observed in react-instantsearch 4.0.3 and 4.0.4.


Background

(copied/adapted from discussion on community forum.)

Using the example connector provided in your docs:

const connectLoading = createConnector({
  displayName: 'ConditionalError',
  getProvidedProps(props, searchState, searchResults) {
    return {
      loading: searchResults.searching
    };
  }
});

I then create a component that renders a message when loading, and its children when complete:

const LoadingMessage = connectLoading(({ loading, children }) =>
  <div>
    {loading ? <p>Loading...</p> : children }
  </div>
);

Now we make use of it in a minimal UI where we just display the stats:

<InstantSearch ...>
  <SearchBox />

  <div className="SearchResults">
    <LoadingMessage>
      <p>FINISHED!</p>
      <Stats />
    </LoadingMessage>
  </div>

</InstantSearch>

You can see this example here in a codepen: https://codepen.io/wrksprfct/full/jwmmox/. This works as expected. Load the page and you see “Loading…” and then “FINISHED! X results found in Yms”. Ensure you have web console open and you’ll see the XHRs fly by and searchResults.searching toggle between true and false when things change state. Type “avocado” into the box: the UI updates and the console logs as you’d expect.

Now here’s the fun part. Let’s replace the <Stats /> component with the <Hits /> component, so it looks like this:

<InstantSearch ...>
  <SearchBox />

  <div className="SearchResults">
    <LoadingMessage>
      <p>FINISHED!</p>
      <Hits />
    </LoadingMessage>
  </div>
</InstantSearch>

You can see this example in another codepen: https://codepen.io/wrksprfct/full/yXbXNQ. !!!BEWARE!!! before loading that page, be prepared to kill it via Chrome’s Task Manager or your OS’s equivalent because it gets into an infinite loop and consumes CPU & gobs of memory quickly. Again, ensure you have the web console open.

So, upon loading that page, you’ll see two XHRs instead of one (identical, AFAICT), and then searchResults.searching flip between false and true ad infinitum.

If, in this problematic example, you hardcode the loading prop to false:

getProvidedProps(props, searchState, searchResults) {
  return {
    loading: false
  };
}

…everything works. Well, except for seeing the loading message… which is the whole point of this exercise!

Again, the only difference is the switch from <Stats /> to <Hits />. I looked at their sources, but could not find anything interesting.

Interestingly, the same problematic behavior occurs with <Pagination />. See https://codepen.io/wrksprfct/full/zzzOWm/. I did not test any others yet.

Now… if we update the LoadingMessage component to not render children:

const LoadingMessage = connectLoading(({ loading }) =>
  <div>
    {loading ? <p>Loading...</p> : <p>DONE!</p> }
  </div>
);

… then everything works fine:

<InstantSearch ...>
  <SearchBox />

  <div className="SearchResults">
    <LoadingMessage />
  </div>
</InstantSearch>

You can see this example in action in this codepen: https://codepen.io/wrksprfct/full/WOOajx.

Issue Analytics

  • State:open
  • Created 6 years ago
  • Reactions:11
  • Comments:14 (4 by maintainers)

github_iconTop GitHub Comments

9reactions
charlypolycommented, Dec 28, 2017

I had the same kind of issue with :

export const SearchResults = connectStateResults(connectHits(SearchResultsComponent));

The workaround was to do :

export const SearchResults = connectHits(connectStateResults(SearchResultsComponent));

💯

8reactions
samousscommented, Mar 8, 2018

@blech75 @roma98 @reinvanimschoot

We are currently looking into the issue, we found where the problem came from. But for the moment we don’t have a proper fix. The problem is inherent of how React InstantSearch works. It’s related to how the query parameters are computed & the lifecycle of the components.

As a workaround for the example provided by @blech75 you need to avoid to mount / unmount the component on each change of the props searching. You can simply hide the Hits component with the help of styles instead of remove it on each changes. The fact that the component is mounted / unmounted trigger the infinite loop. I made an example showing how to avoid the problem with the workaround proposed above.

const Loading = connectStateResults(({ searching, children }) => (
  <div>
    <p style={{ display: searching ? 'block' : 'none' }}>
      Loading...
    </p>

    <div style={{ display: searching ? 'none' : 'block' }}>
      {children}
    </div>
  </div>
));

const App = () => (
  <InstantSearch>
    <Loading>
      <Hits hitComponent={Product} />
      <Pagination />
    </Loading>
  </InstantSearch>
);

https://codesandbox.io/s/7yzwl0kom0

Read more comments on GitHub >

github_iconTop Results From Across the Web

infinite loop when rendering a conditional loading message ...
causes an infinite loop in certain circumstances. when children contains the <Stats /> component, searchResults. searching is set as expected.
Read more >
How to Solve the Infinite Loop of React.useEffect()
1.1 Fixing dependencies. The infinite loop is fixed by correct management of the useEffect(callback, dependencies) dependencies argument. ...
Read more >
"Error: Too many re-renders. React limits the number of ...
The reason for the infinite loop is because something (most likely setState ) in the event callback is triggering a re-render.
Read more >
3 ways to cause an infinite loop in React - Alex Sidorenko
Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. Here are 3 potential causes of the ......
Read more >
Too many re-renders. React limits the number of renders to ...
The error Too many re-renders. React limits the number of renders to prevent an infinite loop occurs for multiple reasons - calling a...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found