Skip to content

Commit 7ff6737

Browse files
committed
fix(state): change behaviour of preserveSharedStateOnUnmount (#6488)
With preserveSharedStateOnUnmount behavior, UI State is the source of truth, when a widget gets removed, the UI State is evaluated again and the parameters result out of that. [FX-3192] Not done in this PR yet: - changed the signature of dispose (no more helper and state) - implement a similar behavior for recommend BREAKING CHANGE: The option `future.preserveSharedStateOnUnmount` is removed and now behaves as if it was set to `true`
1 parent 3d3555d commit 7ff6737

File tree

9 files changed

+22
-173
lines changed

9 files changed

+22
-173
lines changed

examples/react/next/pages/index.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,6 @@ export default function HomePage({ serverState, url }: HomePageProps) {
6767
}),
6868
}}
6969
insights={true}
70-
future={{
71-
preserveSharedStateOnUnmount: true,
72-
}}
7370
>
7471
<div className="Container">
7572
<div>

packages/create-instantsearch-app/e2e/__snapshots__/templates.test.js.snap

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1900,7 +1900,6 @@ const searchClient = algoliasearch('appId', 'apiKey');
19001900
const search = instantsearch({
19011901
indexName: 'indexName',
19021902
searchClient,
1903-
future: { preserveSharedStateOnUnmount: true },
19041903
insights: true,
19051904
});
19061905
@@ -4125,8 +4124,6 @@ import './App.css';
41254124
41264125
const searchClient = algoliasearch('appId', 'apiKey');
41274126
4128-
const future = { preserveSharedStateOnUnmount: true };
4129-
41304127
export function App() {
41314128
return (
41324129
<div>
@@ -4146,7 +4143,6 @@ export function App() {
41464143
<InstantSearch
41474144
searchClient={searchClient}
41484145
indexName=\\"indexName\\"
4149-
future={future}
41504146
insights
41514147
>
41524148
<Configure hitsPerPage={8} />
@@ -5218,7 +5214,6 @@ export default {
52185214
data() {
52195215
return {
52205216
searchClient: algoliasearch('appId', 'apiKey'),
5221-
future: { preserveSharedStateOnUnmount: true },
52225217
};
52235218
},
52245219
};

packages/create-instantsearch-app/src/templates/InstantSearch.js/src/app.js.hbs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ const searchClient = algoliasearch('{{appId}}', '{{apiKey}}');
1414
const search = instantsearch({
1515
indexName: '{{indexName}}',
1616
searchClient,
17-
future: { preserveSharedStateOnUnmount: true },
1817
{{#if flags.insights}}insights: true,{{/if}}
1918
});
2019

@@ -174,4 +173,4 @@ function isModifierEvent(event) {
174173
event.shiftKey
175174
);
176175
}
177-
{{/if}}
176+
{{/if}}

packages/create-instantsearch-app/src/templates/React InstantSearch/src/App.tsx.hbs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ const searchClient = algoliasearch(
3131
'{{apiKey}}'
3232
);
3333

34-
const future = { preserveSharedStateOnUnmount: true };
35-
3634
export function App() {
3735
return (
3836
<div>
@@ -49,7 +47,7 @@ export function App() {
4947
</header>
5048

5149
<div className="container">
52-
<InstantSearch searchClient={searchClient} indexName="{{indexName}}" future={future} {{#if flags.insights}}insights{{/if}}>
50+
<InstantSearch searchClient={searchClient} indexName="{{indexName}}" {{#if flags.insights}}insights{{/if}}>
5351
<Configure hitsPerPage={8} />
5452
<div className="search-panel">
5553
<div className="search-panel__filters">

packages/create-instantsearch-app/src/templates/Vue InstantSearch with Vue 3/src/App.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ export default {
9191
data() {
9292
return {
9393
searchClient: algoliasearch('{{appId}}', '{{apiKey}}'),
94-
future: { preserveSharedStateOnUnmount: true },
9594
};
9695
},
9796
};

packages/instantsearch-core/src/instantsearch.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,7 @@ function defaultCreateURL() {
5050

5151
export const INSTANTSEARCH_FUTURE_DEFAULTS: Required<
5252
InstantSearchOptions['future']
53-
> = {
54-
preserveSharedStateOnUnmount: false,
55-
};
53+
> = {};
5654

5755
/**
5856
* The actual implementation of the InstantSearch. This is
@@ -151,19 +149,6 @@ See ${createDocumentationLink({
151149
})}`
152150
);
153151

154-
if (__DEV__ && options.future?.preserveSharedStateOnUnmount === undefined) {
155-
// eslint-disable-next-line no-console
156-
console.info(`Starting from the next major version, InstantSearch will change how widgets state is preserved when they are removed. InstantSearch will keep the state of unmounted widgets to be usable by other widgets with the same attribute.
157-
158-
We recommend setting \`future.preserveSharedStateOnUnmount\` to true to adopt this change today.
159-
To stay with the current behaviour and remove this warning, set the option to false.
160-
161-
See documentation: ${createDocumentationLink({
162-
name: 'instantsearch',
163-
})}#widget-param-future
164-
`);
165-
}
166-
167152
this.client = searchClient;
168153
this.future = future;
169154
this.indexName = indexName;

packages/instantsearch-core/src/types/instantsearch.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -90,18 +90,7 @@ export type InstantSearchOptions<
9090
* @default false
9191
*/
9292
insights?: InsightsProps | boolean;
93-
future?: {
94-
/**
95-
* Changes the way `dispose` is used in InstantSearch lifecycle.
96-
*
97-
* If `false` (by default), each widget unmounting will remove its state as well, even if there are multiple widgets reading that UI State.
98-
*
99-
* If `true`, each widget unmounting will only remove its own state if it's the last of its type. This allows for dynamically adding and removing widgets without losing their state.
100-
*
101-
* @default false
102-
*/
103-
preserveSharedStateOnUnmount?: boolean; // @MAJOR remove option, only keep the "true" behaviour
104-
};
93+
future?: Record<string, never>;
10594
};
10695

10796
export type InstantSearchStatus = 'idle' | 'loading' | 'stalled' | 'error';

packages/instantsearch-core/src/widgets/__tests__/index-widget.test.ts

Lines changed: 10 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -603,11 +603,9 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index-widge
603603
hitsPerPage: 5,
604604
});
605605

606-
instance.addWidgets([
607-
configureTopLevel,
608-
configureSubLevel,
609-
virtualSearchBox({}),
610-
]);
606+
const searchBox = virtualSearchBox({});
607+
608+
instance.addWidgets([configureTopLevel, configureSubLevel, searchBox]);
611609

612610
instance.init(
613611
createIndexInitOptions({
@@ -628,12 +626,12 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index-widge
628626
})
629627
);
630628

631-
instance.removeWidgets([configureSubLevel]);
629+
instance.removeWidgets([searchBox]);
632630

633631
expect(instance.getHelper()!.state).toEqual(
634632
new SearchParameters({
635633
index: 'indexName',
636-
query: 'Apple iPhone',
634+
hitsPerPage: 5,
637635
distinct: true,
638636
})
639637
);
@@ -646,111 +644,9 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index-widge
646644
).toHaveBeenCalledTimes(2);
647645
});
648646

649-
it('cleans shared refinements when `preserveSharedStateOnUnmount` is unset', () => {
647+
it('preserves shared refinements', () => {
650648
const instance = index({ indexName: 'indexName' });
651-
const instantSearchInstance = createInstantSearch();
652-
653-
const refinementList1 = virtualRefinementList({
654-
attribute: 'brand',
655-
});
656-
657-
const refinementList2 = virtualRefinementList({
658-
attribute: 'brand',
659-
});
660-
661-
instance.addWidgets([refinementList1, refinementList2]);
662-
663-
instance.init(
664-
createIndexInitOptions({
665-
instantSearchInstance,
666-
parent: null,
667-
})
668-
);
669-
670-
// Simulate a state change
671-
instance.getHelper()!.addDisjunctiveFacetRefinement('brand', 'Apple');
672-
673-
expect(instance.getHelper()!.state).toEqual(
674-
new SearchParameters({
675-
index: 'indexName',
676-
maxValuesPerFacet: 10,
677-
disjunctiveFacets: ['brand'],
678-
disjunctiveFacetsRefinements: {
679-
brand: ['Apple'],
680-
},
681-
})
682-
);
683-
684-
instance.removeWidgets([refinementList2]);
685-
686-
expect(instance.getHelper()!.state).toEqual(
687-
new SearchParameters({
688-
index: 'indexName',
689-
maxValuesPerFacet: 10,
690-
disjunctiveFacets: ['brand'],
691-
disjunctiveFacetsRefinements: {
692-
brand: [],
693-
},
694-
})
695-
);
696-
});
697-
698-
it('cleans shared refinements when `preserveSharedStateOnUnmount` is false', () => {
699-
const instance = index({ indexName: 'indexName' });
700-
const instantSearchInstance = createInstantSearch({
701-
future: { preserveSharedStateOnUnmount: false },
702-
});
703-
704-
const refinementList1 = virtualRefinementList({
705-
attribute: 'brand',
706-
});
707-
708-
const refinementList2 = virtualRefinementList({
709-
attribute: 'brand',
710-
});
711-
712-
instance.addWidgets([refinementList1, refinementList2]);
713-
714-
instance.init(
715-
createIndexInitOptions({
716-
instantSearchInstance,
717-
parent: null,
718-
})
719-
);
720-
721-
// Simulate a state change
722-
instance.getHelper()!.addDisjunctiveFacetRefinement('brand', 'Apple');
723-
724-
expect(instance.getHelper()!.state).toEqual(
725-
new SearchParameters({
726-
index: 'indexName',
727-
maxValuesPerFacet: 10,
728-
disjunctiveFacets: ['brand'],
729-
disjunctiveFacetsRefinements: {
730-
brand: ['Apple'],
731-
},
732-
})
733-
);
734-
735-
instance.removeWidgets([refinementList2]);
736-
737-
expect(instance.getHelper()!.state).toEqual(
738-
new SearchParameters({
739-
index: 'indexName',
740-
maxValuesPerFacet: 10,
741-
disjunctiveFacets: ['brand'],
742-
disjunctiveFacetsRefinements: {
743-
brand: [],
744-
},
745-
})
746-
);
747-
});
748-
749-
it('preserves shared refinements when `preserveSharedStateOnUnmount` is true', () => {
750-
const instance = index({ indexName: 'indexName' });
751-
const instantSearchInstance = createInstantSearch({
752-
future: { preserveSharedStateOnUnmount: true },
753-
});
649+
const instantSearchInstance = createInstantSearch({});
754650

755651
const refinementList1 = virtualRefinementList({
756652
attribute: 'brand',
@@ -816,13 +712,15 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index-widge
816712
expect(widget.dispose).toHaveBeenCalledTimes(0);
817713
});
818714

715+
const stateBefore = instance.getHelper()!.state;
716+
819717
instance.removeWidgets(widgets);
820718

821719
widgets.forEach((widget) => {
822720
expect(widget.dispose).toHaveBeenCalledTimes(1);
823721
expect(widget.dispose).toHaveBeenCalledWith({
824722
helper: instance.getHelper(),
825-
state: instance.getHelper()!.state,
723+
state: stateBefore,
826724
recommendState: instance.getHelper()!.recommendState,
827725
parent: instance,
828726
});

packages/instantsearch-core/src/widgets/index-widget.ts

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -438,8 +438,9 @@ export const index = (widgetParams: IndexWidgetParams): IndexWidget => {
438438
});
439439

440440
if (localInstantSearchInstance && Boolean(widgets.length)) {
441-
const { cleanedSearchState, cleanedRecommendState } = widgets.reduce(
441+
const { cleanedRecommendState } = widgets.reduce(
442442
(states, widget) => {
443+
// @MAJOR remove the "cleanup" part of the dispose method
443444
// the `dispose` method exists at this point we already assert it
444445
const next = widget.dispose!({
445446
helper: helper!,
@@ -462,24 +463,12 @@ export const index = (widgetParams: IndexWidgetParams): IndexWidget => {
462463
}
463464
);
464465

465-
const newState = localInstantSearchInstance.future
466-
.preserveSharedStateOnUnmount
467-
? getLocalWidgetsSearchParameters(localWidgets, {
468-
uiState: localUiState,
469-
initialSearchParameters: new algoliasearchHelper.SearchParameters(
470-
{
471-
index: this.getIndexName(),
472-
}
473-
),
474-
})
475-
: getLocalWidgetsSearchParameters(localWidgets, {
476-
uiState: getLocalWidgetsUiState(localWidgets, {
477-
searchParameters: cleanedSearchState,
478-
helper: helper!,
479-
}),
480-
initialSearchParameters: cleanedSearchState,
481-
});
482-
466+
const newState = getLocalWidgetsSearchParameters(localWidgets, {
467+
uiState: localUiState,
468+
initialSearchParameters: new algoliasearchHelper.SearchParameters({
469+
index: this.getIndexName(),
470+
}),
471+
});
483472
localUiState = getLocalWidgetsUiState(localWidgets, {
484473
searchParameters: newState,
485474
helper: helper!,

0 commit comments

Comments
 (0)