react-instantsearch-core
Version:
⚡ Lightning-fast search for React, by Algolia
94 lines (90 loc) • 5.15 kB
JavaScript
import { useEffect, useRef } from 'react';
import { dequal } from "./dequal.js";
import { use } from "./use.js";
import { useInstantSearchContext } from "./useInstantSearchContext.js";
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect.js";
import { useRSCContext } from "./useRSCContext.js";
import { warn } from "./warn.js";
export function useWidget(_ref) {
var _waitForResultsRef$cu, _waitForResultsRef$cu2;
var widget = _ref.widget,
parentIndex = _ref.parentIndex,
props = _ref.props,
shouldSsr = _ref.shouldSsr,
skipSuspense = _ref.skipSuspense;
var _useRSCContext = useRSCContext(),
waitForResultsRef = _useRSCContext.waitForResultsRef,
countRef = _useRSCContext.countRef,
ignoreMultipleHooksWarning = _useRSCContext.ignoreMultipleHooksWarning;
var prevPropsRef = useRef(props);
useEffect(function () {
prevPropsRef.current = props;
}, [props]);
var prevWidgetRef = useRef(widget);
useEffect(function () {
prevWidgetRef.current = widget;
}, [widget]);
var cleanupTimerRef = useRef(null);
var shouldAddWidgetEarly = shouldSsr && !parentIndex.getWidgets().includes(widget);
var search = useInstantSearchContext();
// This effect is responsible for adding, removing, and updating the widget.
// We need to support scenarios where the widget is remounted quickly, like in
// Strict Mode, so that we don't lose its state, and therefore that we don't
// break routing.
useIsomorphicLayoutEffect(function () {
var previousWidget = prevWidgetRef.current;
// Scenario 1: the widget is added for the first time.
if (!cleanupTimerRef.current) {
if (!shouldSsr) {
parentIndex.addWidgets([widget]);
}
}
// Scenario 2: the widget is rerendered or updated.
else {
// We cancel the original effect cleanup because it may not be necessary if
// props haven't changed. (We manually call it if it is below.)
clearTimeout(cleanupTimerRef.current);
// Warning: if an unstable function prop is provided, `dequal` is not able
// to keep its reference and therefore will consider that props did change.
// This could unsollicitely remove/add the widget, therefore forget its state,
// and could be a source of confusion.
// If users face this issue, we should advise them to provide stable function
// references.
var arePropsEqual = dequal(props, prevPropsRef.current);
// If props did change, then we execute the cleanup function instantly
// and then add the widget back. This lets us add the widget without
// waiting for the scheduled cleanup function to finish (that we canceled
// above).
if (!arePropsEqual) {
parentIndex.removeWidgets([previousWidget]);
parentIndex.addWidgets([widget]);
}
}
return function () {
// We don't remove the widget right away, but rather schedule it so that
// we're able to cancel it in the next effect.
cleanupTimerRef.current = setTimeout(function () {
search._schedule(function () {
if (search._preventWidgetCleanup) return;
parentIndex.removeWidgets([previousWidget]);
});
});
};
}, [parentIndex, widget, shouldSsr, search, props]);
if (shouldAddWidgetEarly || (waitForResultsRef === null || waitForResultsRef === void 0 ? void 0 : (_waitForResultsRef$cu = waitForResultsRef.current) === null || _waitForResultsRef$cu === void 0 ? void 0 : _waitForResultsRef$cu.status) === 'pending') {
parentIndex.addWidgets([widget]);
}
if (waitForResultsRef !== null && waitForResultsRef !== void 0 && waitForResultsRef.current && !skipSuspense) {
var _search$helper;
use(waitForResultsRef.current);
// If we made a second request because of DynamicWidgets, we need to wait for the second result,
// except for DynamicWidgets itself which needs to render its children after the first result.
if (widget.$$type !== 'ais.dynamicWidgets' && (_search$helper = search.helper) !== null && _search$helper !== void 0 && _search$helper.lastResults) {
use(waitForResultsRef.current);
}
}
if ((waitForResultsRef === null || waitForResultsRef === void 0 ? void 0 : (_waitForResultsRef$cu2 = waitForResultsRef.current) === null || _waitForResultsRef$cu2 === void 0 ? void 0 : _waitForResultsRef$cu2.status) === 'fulfilled') {
countRef.current += 1;
process.env.NODE_ENV === 'development' ? warn(countRef.current > parentIndex.getWidgets().length && !ignoreMultipleHooksWarning, "We detected you may have a component with multiple InstantSearch hooks.\n\nWith Next.js, you need to set `skipSuspense` to `true` for all but the last hook in the component, otherwise, only the first hook will be rendered on the server.\n\nThis warning can be a false positive if you are using dynamic widgets or multi-index, in which case you can ignore it by setting `ignoreMultipleHooksWarning` to `true` in `<InstantSearchNext`.\n\nFor more information, see https://www.algolia.com/doc/guides/building-search-ui/going-further/server-side-rendering/react/#composing-hooks") : void 0;
}
}