diff --git a/packages/instantsearch.js/src/connectors/dynamic-widgets/__tests__/connectDynamicWidgets-test.ts b/packages/instantsearch.js/src/connectors/dynamic-widgets/__tests__/connectDynamicWidgets-test.ts index ad173fb2a7..135dc63716 100644 --- a/packages/instantsearch.js/src/connectors/dynamic-widgets/__tests__/connectDynamicWidgets-test.ts +++ b/packages/instantsearch.js/src/connectors/dynamic-widgets/__tests__/connectDynamicWidgets-test.ts @@ -723,6 +723,7 @@ describe('connectDynamicWidgets', () => { expect( dynamicWidgets.getRenderState(existingRenderState, createInitOptions()) ).toEqual({ + PREVENT_RENDER: true, dynamicWidgets: { attributesToRender: [], widgetParams, @@ -750,6 +751,7 @@ describe('connectDynamicWidgets', () => { createRenderOptions() ) ).toEqual({ + PREVENT_RENDER: true, dynamicWidgets: { attributesToRender: ['test1'], widgetParams, diff --git a/packages/instantsearch.js/src/connectors/dynamic-widgets/connectDynamicWidgets.ts b/packages/instantsearch.js/src/connectors/dynamic-widgets/connectDynamicWidgets.ts index d810c7f344..3feace2cbb 100644 --- a/packages/instantsearch.js/src/connectors/dynamic-widgets/connectDynamicWidgets.ts +++ b/packages/instantsearch.js/src/connectors/dynamic-widgets/connectDynamicWidgets.ts @@ -212,10 +212,22 @@ const connectDynamicWidgets: DynamicWidgetsConnector = }) ); }, + rendersOnPrevented: true, getRenderState(renderState, renderOptions) { + const { PREVENT_RENDER = true } = renderState; + const dynamicWidgets = this.getWidgetRenderState(renderOptions); + + // if we are in a "has results, but only just mounted widgets" state, add a flag + // in other cases the flag isn't set and we *do* render the other widgets + // TODO: improve this condition, reordering is allowed, we just want to make sure it's the same items + const willChangeWidgets = + renderState.dynamicWidgets?.attributesToRender.join('__') !== + dynamicWidgets?.attributesToRender.join('__'); + return { ...renderState, - dynamicWidgets: this.getWidgetRenderState(renderOptions), + PREVENT_RENDER: PREVENT_RENDER !== false && willChangeWidgets, + dynamicWidgets, }; }, getWidgetRenderState({ results, state }) { diff --git a/packages/instantsearch.js/src/types/render-state.ts b/packages/instantsearch.js/src/types/render-state.ts index 4064993dee..1f531bc178 100644 --- a/packages/instantsearch.js/src/types/render-state.ts +++ b/packages/instantsearch.js/src/types/render-state.ts @@ -55,8 +55,10 @@ type ConnectorRenderStates = AnswersWidgetDescription['indexRenderState'] & type WidgetRenderStates = AnalyticsWidgetDescription['indexRenderState'] & PlacesWidgetDescription['indexRenderState']; +type GlobalRenderStates = { PREVENT_RENDER: boolean }; + export type IndexRenderState = Partial< - ConnectorRenderStates & WidgetRenderStates + ConnectorRenderStates & WidgetRenderStates & GlobalRenderStates >; export type RenderState = { diff --git a/packages/instantsearch.js/src/types/widget.ts b/packages/instantsearch.js/src/types/widget.ts index b8ad3e0b5f..0adf7fd48e 100644 --- a/packages/instantsearch.js/src/types/widget.ts +++ b/packages/instantsearch.js/src/types/widget.ts @@ -247,6 +247,12 @@ type RequiredRenderStateLifeCycle< >, renderOptions: InitOptions | RenderOptions ) => IndexRenderState & TWidgetDescription['indexRenderState']; + + /** + * If PREVENT_RENDER is set in the render state, still render this widget. + * This is only useful for cases where this widget is the one setting PREVENT_RENDER + */ + rendersOnPrevented?: boolean; }; type RenderStateLifeCycle< diff --git a/packages/instantsearch.js/src/widgets/index/index.ts b/packages/instantsearch.js/src/widgets/index/index.ts index 97912b2bc1..5336439e49 100644 --- a/packages/instantsearch.js/src/widgets/index/index.ts +++ b/packages/instantsearch.js/src/widgets/index/index.ts @@ -644,6 +644,12 @@ const index = (widgetParams: IndexWidgetParams): IndexWidget => { } }); + // a widget that causes the current state to be incomplete (dynamic widgets) + // can prevent following widgets from rendering this pass by setting the + // PREVENT_RENDER flag to "true" + const { PREVENT_RENDER = false } = + instantSearchInstance.renderState?.[this.getIndexId()] ?? {}; + widgetsToRender.forEach((widget) => { // At this point, all the variables used below are set. Both `helper` // and `derivedHelper` have been created at the `init` step. The attribute @@ -652,7 +658,8 @@ const index = (widgetParams: IndexWidgetParams): IndexWidget => { // be delayed. The render is triggered for the complete tree but some parts do // not have results yet. - if (widget.render) { + // the widget can make itself rendering, even if the rest is prevented + if (widget.render && (!PREVENT_RENDER || widget.rendersOnPrevented)) { widget.render(createRenderArgs(instantSearchInstance, this)); } });