algolia / instantsearch

⚑️ Libraries for building performant and instant search experiences with Algolia. Compatible with JavaScript, TypeScript, React and Vue.

Home Page:https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/js/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

TypeError: Cannot read property 'state' of null

TristanMaurier98 opened this issue Β· comments

πŸ› Current behavior

On the sentry of our production app we have this error arriving, it seems to produce crashes but impossible to reproduce locally:

TypeError: Cannot read property 'state' of null
at anonymous(node_modules/instantsearch.js/cjs/widgets/index/index.js:447:26)
at forEach(native)
at dispose(node_modules/instantsearch.js/cjs/widgets/index/index.js:437:27)
at anonymous(node_modules/instantsearch.js/cjs/widgets/index/index.js:219:36)
at reduce(native)
at removeWidgets(node_modules/instantsearch.js/cjs/widgets/index/index.js:217:40)
at anonymous(node_modules/react-instantsearch-core/dist/cjs/lib/useWidget.js:74:36)
at anonymous(node_modules/react-instantsearch-core/dist/cjs/lib/useInstantSearchApi.js:47:19)
at forEach(native)
at anonymous(node_modules/react-instantsearch-core/dist/cjs/lib/useInstantSearchApi.js:46:39)
at apply(native)
at anonymous(node_modules/react-native/Libraries/Core/Timers/JSTimers.js:213:23)
at _callTimer(node_modules/react-native/Libraries/Core/Timers/JSTimers.js:111:15)
at callTimers(node_modules/react-native/Libraries/Core/Timers/JSTimers.js:359:17)
at apply(native)
at __callFunction(node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:427:32)
at anonymous(node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:113:26)
at __guard(node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:368:11)
at callFunctionReturnFlushedQueue(node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:112:17)

For more context we are running Γ  cross platform react-native app, and this error seems to appears at least on ios and android.

πŸ” Steps to reproduce

I don't have any, I would be more than glad to share more information.

Live reproduction

/

πŸ’­ Expected behavior

Not having this crashes.

Package version

"instantsearch.js": "^4.57.0", "react-instantsearch": "^7.0.0", "react-instantsearch-core": "^7.1.0",

Operating system

No response

Browser

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct

If I read the stack trace correctly, that would be related to

localWidgets.forEach((widget) => {
if (widget.dispose) {
// The dispose function is always called once the instance is started
// (it's an effect of `removeWidgets`). The index is initialized and
// the Helper is available. We don't care about the return value of
// `dispose` because the index is removed. We can't call `removeWidgets`
// because we want to keep the widgets on the instance, to allow idempotent
// operations on `add` & `remove`.
widget.dispose({
helper: helper!,
state: helper!.state,
parent: this,
});
}
});
, thus when an index widget unmounts. I don't have an idea right now why helper would be null, but I imagine the index widget unmounts before init is ever called? In which case we probably should do less work.

Do you have any more information at all as to when this happens? Are there any unstable references maybe that could cause a remount of an index widget? What widgets do you have mounted, is anything conditional?

Most likely I'd say this is happening on a quick page navigation, or a similar situation in which InstantSearch no longer will be mounted after the error, making the situation less "problematic", but I'm definitely not sure.

From what I can see in my code we only use the react-instantsearch-core lib with the hook: useSearchBox in a rather classic way.

If we look in a little more detail at the stack trace it looks like it goes from react-instantsearch-core to instentsearch.js

I'd say this probably happens in the unmounting phase / navigating. Can you reproduce the error then @TristanMaurier98 ?

I'm having great difficulty reproducing the bug but the project I'm working on is open source, here is the link to the file which I think is the cause of the problem.
https://github.com/pass-culture/pass-culture-app-native/blob/master/src/features/search/components/SearchBox/SearchBox.tsx
Maybe this will help you identify a design flaw.

We've been experiencing this same issue, but only when we're using the InstantSearchNext component with a nested Index inside of it. As mentioned above, something happens in that cleanup process that makes state unavailable to that widget when it's disposed.

To get around it for now, I installed patch-package and uploaded the following file. Not ideal, but keeps the project going while this is looked into further. Hope this helps @TristanMaurier98

diff --git a/node_modules/react-instantsearch-core/dist/es/lib/useInstantSearchApi.js b/node_modules/react-instantsearch-core/dist/es/lib/useInstantSearchApi.js
index 8909dd5..d5cda33 100644
--- a/node_modules/react-instantsearch-core/dist/es/lib/useInstantSearchApi.js
+++ b/node_modules/react-instantsearch-core/dist/es/lib/useInstantSearchApi.js
@@ -144,7 +144,11 @@ export function useInstantSearchApi(props) {
     }
     return function () {
       function cleanup() {
+        try {
         search.dispose();
+        } catch(e) {
+          console.warn(`Failed to dispose`, e)
+        }
       }
       clearTimeout(search._schedule.timer);
       // We clean up only when the component that uses this subscription unmounts,

Are you also using React Native @MikeIbberson? Maybe that could be a common thread (although I still don't see the link, sorry!)?

If someone could get a live stack trace, sentry, or when it happens a sort of reproduction, that would be the way to go to find the root cause.

In the very least, I guess what we can do is first check if the index widget's init has even been called before dispose happens, and skip the dispose of the child widgets, but I'm not sure if that couldn't cause other downstream problems.

One thing I think may be possible in the app, especially linked to

https://github.com/pass-culture/pass-culture-app-native/blob/d934c32f1fde774b1db00884a740c98dc2e29462/src/features/search/pages/SearchLanding/SearchLanding.tsx#L56-L58

@TristanMaurier98 is that maybe netInfo flickers on/off and the initial state is "enabled", then InstantSearch mounts, but immediately after it's "disabled" and InstantSearch unmounts, before the child widgets properly have mounted.

We're using React 18 (JS not native). We're also using the latest versions of all Algolia packages, though I tried downgrading to releases from last year and observed the same issues.

I will provide more context next week, as there's more development I need to do on this feature and can start building a case. Some things I've noted so far, though, anecdotally:

  1. I think there's a race condition in the <Index /> component (or, more specifically, the useIndex or useWidget hooks). For NextJS, when navigating page-to-page, the <InstantSearchNext /> component seems to be destroying that state before Index has a chance to unmount. Strangely, without that nested Index, no errors.
  2. Inside <InstantSearchNext /> I'm also seeing some issues with multi-index querying that might be responsible for this initial issue. In our app, we load about 7 different queries, all of which are using a unique Index id (some duplicated index names), but the JSON payload that gets rendered for the SSR provider sheds most of the results. It appears that the server state only reflects the query that takes the longest to execute, so without fail we see hydration errors.

I'll provide proper code snippets as we dig into this more next week, but that's some initial information on what's going on.

Are you also using React Native @MikeIbberson? Maybe that could be a common thread (although I still don't see the link, sorry!)?

If someone could get a live stack trace, sentry, or when it happens a sort of reproduction, that would be the way to go to find the root cause.

In the very least, I guess what we can do is first check if the index widget's init has even been called before dispose happens, and skip the dispose of the child widgets, but I'm not sure if that couldn't cause other downstream problems.

One thing I think may be possible in the app, especially linked to

pass-culture/pass-culture-app-native@d934c32/src/features/search/pages/SearchLanding/SearchLanding.tsx#L56-L58

@TristanMaurier98 is that maybe netInfo flickers on/off and the initial state is "enabled", then InstantSearch mounts, but immediately after it's "disabled" and InstantSearch unmounts, before the child widgets properly have mounted.

I have try to reproduce by flicking the netinfo but nothing happen (in ios, android and web).
I can sare with you this sentry link https://sentry.passculture.team/share/issue/33f5957c51d04cbab91fe81f7566f26b/
with the precise stack trace.

Current I'm using the Index inside InstantSearchNextjs and it happens like @MikeIbberson explained above. When I change to use normal InstantSearch the error message is gone.

            <InstantSearchNext
              future={{ preserveSharedStateOnUnmount: true }}
              searchClient={searchClient}
              routing
            >
              <Configure hitsPerPage={5} />
              <SearchBox />
              <Index indexName="leads">
                <Hits />
              </Index>
              <Index indexName="cases">
                <Hits />
              </Index>
              <Index indexName="users">
                <Hits />
              </Index>
            </InstantSearchNext>

The error message:

Uncaught TypeError: helper is null
    dispose index.js:449
    dispose index.js:439
    cleanedState index.js:212
    removeWidgets index.js:210
    removeWidgets InstantSearch.js:354
[index.js:449](webpack://_N_E/node_modules/.pnpm/instantsearch.js@4.66.1_algoliasearch@4.23.2/node_modules/instantsearch.js/es/widgets/index/index.js?77c0)
    dispose index.js:449
    forEach self-hosted:203
    dispose index.js:439
    cleanedState index.js:212
    reduce self-hosted:263
    removeWidgets index.js:210
    removeWidgets InstantSearch.js:354
    dispose InstantSearch.js:522
    cleanup useInstantSearchApi.js:147
    sentryWrapped helpers.js:91
    (Async: setTimeout handler)
    _wrapTimeFunction trycatch.js:106
    store useInstantSearchApi.js:156
    safelyCallDestroy react-dom.development.js:20802
    commitHookEffectListUnmount react-dom.development.js:20984
...

I have the same problem in my NextJS application when using multi indices and widget :

index.js:513 Uncaught TypeError: Cannot read properties of null (reading 'state')
at eval (index.js:513:27)
at Array.forEach ()
at Object.dispose (index.js:503:20)
at eval (index.js:246:29)
at Array.reduce ()
at Object.removeWidgets (index.js:244:36)
at InstantSearch.removeWidgets (InstantSearch.js:373:22)
at InstantSearch.dispose (InstantSearch.js:541:12)
at cleanup (useInstantSearchApi.js:162:16)

Using @sangdth solution (using InstantSearch component instead of InstantSearchNext component) removes the error.

Hi, I've made a pull request #6173 that should fix this issue, feel free to try it out and give feedback!