react-instantsearch
react-instantsearch copied to clipboard
Question: how to detect and handle errors? (especially apiKey)
We are building an SPA which uses Algolia for search-related features. While we periodically renew users apiKey, cases might happen where the key is expired (user reopens his laptop and starts searching right away before any renewal can be made).
I've searched through the documentation but could not find a way to hook an onError
callback or even detect when there are similar issues.
Does anyone have pointers?
A regular error should be thrown when API key is incorrect. In that case you can use an Error boundary around the <InstantSearch />
see https://reactjs.org/docs/error-boundaries.html
You can also use the connector connectStateResults
to access the error.
@Haroenv I tried using React error boundaries with success:
class SearchContainer extends React.Component {
componentDidCatch(error) {
debugger
// renew searchToken
}
render() {
const { searchToken, searchState, searchApplicationId } = this.props
if (!searchToken) {
return false
}
const query = cleanSearchQuery(searchState.get('query'))
return (
<InstantSearch
appId={searchApplicationId}
apiKey="obviously-invalid" //{searchToken}
indexName="notes"
root={{
Root: 'div',
props: {
className: 'flexbox flexbox-no-grow flexbox-no-shrink',
style: { width: 300 },
},
}}
searchState={{ query, hitsPerPage: HITS_PER_PAGE }}
>
<SearchPanel />
</InstantSearch>
)
}
}
I can see the 403s in the debug tools but the debugger isn't stopping and no logs show in my console. This isn't so surprising as I guess these network errors are asynchronous and can't be caught by React (unless InstantSearch specifically rethrows them in a way React can catch).
I coupled it with @samouss solution and it worked but it feels clumsy:
class SearchContainer extends Component {
componentDidCatch(error, info) {
debugger
// renew searchToken
}
render() {
const { searchToken, searchState, searchApplicationId } = this.props
if (!searchToken) {
return false
}
const query = cleanSearchQuery(searchState.get('query'))
return (
<InstantSearch
appId={searchApplicationId}
apiKey="obviously-invalid" //{searchToken}
indexName="notes"
root={{
Root: 'div',
props: {
className: 'flexbox flexbox-no-grow flexbox-no-shrink',
style: { width: 300 },
},
}}
searchState={{ query, hitsPerPage: HITS_PER_PAGE }}
>
<SearchErrorCatcher />
<SearchPanel />
</InstantSearch>
)
}
}
const SearchErrorCatcher = connectStateResults(
class SearchErrorCatcher extends Component {
error = null
componentWillReceiveProps(nextProps) {
if (this.error && !nextProps.error) {
this.error = null
}
if (!this.error && nextProps.error) {
// only throw on new errors
throw nextProps.error
}
}
render() {
return null
}
}
)
@ArnaudRinquin Thanks for sharing your solution. At the end you don't even need to re-throw the error. You can call a function that will renew the apiKey
directly from willReceiveProps
.
Oh I know I don't have to, but it feels cleaner.
Anyway, It'd be nice to have a proper Error handling as this solution seems clunky. @Haroenv don't you think the Error Boundary not working is a bug and should be addressed? An onError
prop on the InstantSearch
could work as well.
The error boundaries are used to catch error related to the rendering. This one is not related to the rendering, you can recover it independently from React (see Error Boundaries).
You are right a prop onError
could also work, I like the approach with the declarative SearchErrorCatcher
. It act a bit like an error boundaries at the end, it catch the network error of InstantSearch. We could provide a widget out of the box it will feels a bit more natural.
Thanks for the work @ArnaudRinquin
const SearchErrorCatcher = connectStateResults(
class SearchErrorCatcher extends Component {
error = null
componentWillReceiveProps(nextProps) {
if (this.error && !nextProps.error) {
this.error = null
}
if (!this.error && nextProps.error) {
// only throw on new errors
throw nextProps.error
}
}
render() {
return null
}
}
)
If I try your approche I get an TypeError:
Unhandled Runtime Error
TypeError: this.props.contextValue.store.getState is not a function
Call Stack
Connector.getProvidedProps
node_modules/react-instantsearch-core/dist/es/core/createConnector.js (186:0)
new Connector
node_modules/react-instantsearch-core/dist/es/core/createConnector.js (63:0)
constructClassInstance
node_modules/react-dom/cjs/react-dom.development.js (12880:0)
updateClassComponent
node_modules/react-dom/cjs/react-dom.development.js (17100:0)
beginWork
node_modules/react-dom/cjs/react-dom.development.js (18620:0)
HTMLUnknownElement.callCallback
node_modules/react-dom/cjs/react-dom.development.js (188:0)
Object.invokeGuardedCallbackDev
node_modules/react-dom/cjs/react-dom.development.js (237:0)
invokeGuardedCallback
node_modules/react-dom/cjs/react-dom.development.js (292:0)
beginWork$1
node_modules/react-dom/cjs/react-dom.development.js (23203:0)
performUnitOfWork
node_modules/react-dom/cjs/react-dom.development.js (22154:0)
workLoopSync
node_modules/react-dom/cjs/react-dom.development.js (22130:0)
performSyncWorkOnRoot
node_modules/react-dom/cjs/react-dom.development.js (21756:0)
eval
node_modules/react-dom/cjs/react-dom.development.js (11089:0)
unstable_runWithPriority
node_modules/scheduler/cjs/scheduler.development.js (653:0)
runWithPriority$1
node_modules/react-dom/cjs/react-dom.development.js (11039:0)
flushSyncCallbackQueueImpl
node_modules/react-dom/cjs/react-dom.development.js (11084:0)
flushSyncCallbackQueue
node_modules/react-dom/cjs/react-dom.development.js (11072:0)
flushPassiveEffectsImpl
node_modules/react-dom/cjs/react-dom.development.js (22883:0)
unstable_runWithPriority
node_modules/scheduler/cjs/scheduler.development.js (653:0)
runWithPriority$1
node_modules/react-dom/cjs/react-dom.development.js (11039:0)
flushPassiveEffects
node_modules/react-dom/cjs/react-dom.development.js (22820:0)
eval
node_modules/react-dom/cjs/react-dom.development.js (22699:0)
workLoop
node_modules/scheduler/cjs/scheduler.development.js (597:0)
flushWork
node_modules/scheduler/cjs/scheduler.development.js (552:0)
MessagePort.performWorkUntilDeadline
node_modules/scheduler/cjs/scheduler.development.js (164:0)
Any idea how to fix this?
I'm currently using:
- "react": "^16.13.1"
- "next": "^9.4.4"
- "typesense": "^1.3.0"
- "typesense-instantsearch-adapter": "^2.4.1"
- "react-instantsearch-dom": "^6.28.0"
- "react-instantsearch-hooks-web": "^6.28.0"
You can't mix React InstantSearch dom and React InstantSearch hooks web @henrikhertler, there should be a clean error thrown but I guess it's not visible because you used the root from React InstantSearch dom
@Haroenv What would the equivalent hook for connectStateResults
be then? I can't find anything that matches it on the upgrade guide, or no widget in the list that seems to have a state that contains error
.
I also can't find a hook that exposes searching
either. Would I have to useConnector
to connectStateResults
from react-instantsearch-core
? The example on how to do this, shows connecting to a connector from instantsearch.js/es/connectors
, but it seems there is no connectStateResults
connector in the connectors directory over there?
Maybe you can see, I'm a bit lost!
Hi @carlreid, to handle errors, please follow this guide. It will show you how to add a middleware to InstantSearch and retrieve the error information.
Regarding searching
, there is no strict equivalent in React InstantSearch Hooks, but you can use isSearchStalled
returned by the useSearchBox()
Hook. It will set to true
when no results are returned after 200ms by default. If necessary, you can fine tune this value with stalledSearchDelay
on <InstantSearch>
.
@dhayab Thanks, I'll check it out. Completely missed this "Conditional Display" section when I CTRL + F for errors 😅
Stalled isn't enough in the case we wish to show a spinner while it's searching, so could be nice if it made a return in the future, though not critical to have for now.
Hey!
We're doing a round of clean up before migrating this repository to the new InstantSearch monorepo.
I'm going to close this issue since a solution was provided by @dhayab.
We now have a Status API returned by the useInstantSearch()
hook that could be useful for your use-case.