diff --git a/examples/react/next-app-router/app/layout.tsx b/examples/react/next-app-router/app/layout.tsx index 7ad8b7d4a8c..9f2f7b34fe2 100644 --- a/examples/react/next-app-router/app/layout.tsx +++ b/examples/react/next-app-router/app/layout.tsx @@ -13,12 +13,6 @@ export default function RootLayout({ }) { return ( - - - {children} ); diff --git a/examples/react/next-app-router/app/page.tsx b/examples/react/next-app-router/app/page.tsx index f74d2677e0d..1d0aac959c7 100644 --- a/examples/react/next-app-router/app/page.tsx +++ b/examples/react/next-app-router/app/page.tsx @@ -8,9 +8,12 @@ import { Highlight, SearchBox, RefinementList, + DynamicWidgets, } from 'react-instantsearch'; import { NextInstantSearchSSR } from 'react-instantsearch-ssr-nextjs'; +import { Panel } from '../components/Panel'; + const client = algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76'); type HitProps = { @@ -34,7 +37,7 @@ export default function SearchPage() {
- +
@@ -44,3 +47,11 @@ export default function SearchPage() { ); } + +function FallbackComponent({ attribute }: { attribute: string }) { + return ( + + + + ); +} diff --git a/packages/react-instantsearch-core/src/lib/InstantSearchRSCContext.ts b/packages/react-instantsearch-core/src/lib/InstantSearchRSCContext.ts index 82b84d0a356..851b46bf039 100644 --- a/packages/react-instantsearch-core/src/lib/InstantSearchRSCContext.ts +++ b/packages/react-instantsearch-core/src/lib/InstantSearchRSCContext.ts @@ -3,13 +3,8 @@ import { createContext } from 'react'; import type { PromiseWithState } from './wrapPromiseWithState'; import type { MutableRefObject } from 'react'; -export type InstantSearchRSCContextApi = { - promiseRef: MutableRefObject | null>; - insertHTML: (callbacks: () => React.ReactNode) => void; -}; +export type InstantSearchRSCContextApi = + MutableRefObject | null> | null; export const InstantSearchRSCContext = - createContext({ - promiseRef: { current: null }, - insertHTML: () => {}, - }); + createContext(null); diff --git a/packages/react-instantsearch-core/src/lib/useInstantSearchApi.tsx b/packages/react-instantsearch-core/src/lib/useInstantSearchApi.ts similarity index 85% rename from packages/react-instantsearch-core/src/lib/useInstantSearchApi.tsx rename to packages/react-instantsearch-core/src/lib/useInstantSearchApi.ts index dde61b98f7e..8810839a59e 100644 --- a/packages/react-instantsearch-core/src/lib/useInstantSearchApi.tsx +++ b/packages/react-instantsearch-core/src/lib/useInstantSearchApi.ts @@ -1,20 +1,16 @@ -/* eslint-disable complexity */ import InstantSearch from 'instantsearch.js/es/lib/InstantSearch'; -import { getInitialResults } from 'instantsearch.js/es/lib/server'; -import React, { useCallback, useRef, version as ReactVersion } from 'react'; +import { useCallback, useRef, version as ReactVersion } from 'react'; import { useSyncExternalStore } from 'use-sync-external-store/shim'; -import { useInstantSearchServerContext } from '../lib/useInstantSearchServerContext'; -import { useInstantSearchSSRContext } from '../lib/useInstantSearchSSRContext'; import version from '../version'; import { useForceUpdate } from './useForceUpdate'; +import { useInstantSearchServerContext } from './useInstantSearchServerContext'; +import { useInstantSearchSSRContext } from './useInstantSearchSSRContext'; import { useRSCContext } from './useRSCContext'; import { warn } from './warn'; -import { wrapPromiseWithState } from './wrapPromiseWithState'; import type { - InitialResults, InstantSearchOptions, SearchClient, UiState, @@ -55,30 +51,18 @@ export type InternalInstantSearch< _preventWidgetCleanup?: boolean; }; -const InstantSearchInitialResults = Symbol.for('InstantSearchInitialResults'); -declare global { - interface Window { - [InstantSearchInitialResults]?: InitialResults; - } -} - export function useInstantSearchApi( props: UseInstantSearchApiProps ) { const forceUpdate = useForceUpdate(); const serverContext = useInstantSearchServerContext(); const serverState = useInstantSearchSSRContext(); - const { promiseRef, insertHTML } = useRSCContext(); - let initialResults = - serverState?.initialResults || - (typeof window !== 'undefined' - ? window[InstantSearchInitialResults] - : undefined); + const waitingForResultsRef = useRSCContext(); + const initialResults = serverState?.initialResults; const prevPropsRef = useRef(props); - if (Array.isArray(initialResults)) { - initialResults = initialResults.pop(); - } + const shouldRenderAtOnce = + serverContext || initialResults || waitingForResultsRef; let searchRef = useRef | null>( null @@ -112,7 +96,7 @@ export function useInstantSearchApi( } as typeof search._schedule; search._schedule.queue = []; - if (serverContext || initialResults) { + if (shouldRenderAtOnce) { // InstantSearch.js has a private Initial Results API that lets us inject // results on the search instance. // On the server, we default the initial results to an empty object so that @@ -131,7 +115,7 @@ export function useInstantSearchApi( // On the server, we start the search early to compute the search parameters. // On SSR, we start the search early to directly catch up with the lifecycle // and render. - if (serverContext || initialResults || promiseRef?.current === null) { + if (shouldRenderAtOnce) { search.start(); } @@ -141,26 +125,6 @@ export function useInstantSearchApi( serverContext.notifyServer({ search }); } - if (promiseRef?.current === null && typeof window === 'undefined') { - promiseRef.current = wrapPromiseWithState( - new Promise((resolve) => { - search.once('render', () => { - const results = getInitialResults(search.mainIndex); - insertHTML(() => ( -