Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce initialUiState option #4074

Merged
merged 10 commits into from
Aug 28, 2019
Merged

feat: introduce initialUiState option #4074

merged 10 commits into from
Aug 28, 2019

Conversation

samouss
Copy link
Contributor

@samouss samouss commented Aug 27, 2019

Summary

This PR introduces the initialUiState option at the InstantSearch level. The option allows users to provide an uiState used for the initial request. Only for the initial request. I've chosen to name the option initialXXX rather than uiState to have a clear intent. At some point, we might want to introduce the concept of controlled state (like React InstantSearch) that could use the option uiState.

At the moment the type definitions are incorrect. We have only one type that defines the uiState but we have to define two different types. The first is one is the "global" uiState and the second one is the "local" to each index. The "global" uiState is built on multiple "local" uiState. We'll update the definition in a separate PR.

Usage

const search = instantsearch({
  // ...
  initialUiState: {
    instant_search: {
      query: "Apple"
    },
    instant_search_price_asc: {
      refinementList: {
        brand: ["Apple"]
      }
    }
  }
});

Checkout the example on Storybook.

@samouss samouss requested a review from a team August 27, 2019 19:23
@ghost ghost requested review from Haroenv and tkrugg and removed request for a team August 27, 2019 19:23
Copy link
Contributor

@Haroenv Haroenv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small typo, but works as expected as far as I can tell

src/lib/InstantSearch.ts Outdated Show resolved Hide resolved
src/widgets/index/index.ts Show resolved Hide resolved
stories/instantsearch.stories.js Show resolved Hide resolved
src/lib/InstantSearch.ts Show resolved Hide resolved
@Haroenv
Copy link
Contributor

Haroenv commented Aug 28, 2019

How does it behave when combined with routing on initial load with a state in the url?

Co-Authored-By: Haroen Viaene <hello@haroen.me>
@samouss
Copy link
Contributor Author

samouss commented Aug 28, 2019

How does it behave when combined with routing on initial load with a state in the url?

Nothing is implemented right now since routing is not updated (yet). For a first version I think an override (or shallow merge) will be enough otherwise we have to implement a merge function for UiState. This is something that I would like to avoid as much as possible. Another alternative is to allow the user to provide its own function to perform the merge.

Copy link
Member

@francoischalifour francoischalifour left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initialXXX is also the prefix I would have gone for. I'm wondering if uiState is a concept that users should know about though. On the other hand, state is too vague. For reference, we use searchState in the React implementation, but I'm not convinced this is better.

src/widgets/index/__tests__/index-test.ts Outdated Show resolved Hide resolved
src/widgets/index/__tests__/index-test.ts Outdated Show resolved Hide resolved
src/widgets/index/__tests__/index-test.ts Outdated Show resolved Hide resolved
src/lib/__tests__/InstantSearch-test.js Show resolved Hide resolved
@samouss samouss merged commit 0211b98 into next Aug 28, 2019
@samouss samouss deleted the feat/initial-ui-state branch September 4, 2019 09:33
samouss added a commit that referenced this pull request Sep 17, 2019
**Summary**

This PR introduces the `initialUiState` option at the `InstantSearch` level. The option allows users to provide an `uiState` used for the initial request. Only for the **initial request**. I've chosen to name the option `initialXXX` rather than `uiState` to have a clear intent. At some point, we might want to introduce the concept of controlled state (like React InstantSearch) that could use the option `uiState`.

At the moment the type definitions are incorrect. We have only one type that defines the `uiState` but we have to define two different types. The first is one is the "global" `uiState` and the second one is the "local" to each index. The "global" `uiState` is built on multiple "local" `uiState`. We'll update the definition in a separate PR.


**Usage**

```js
const search = instantsearch({
  // ...
  initialUiState: {
    instant_search: {
      query: "Apple"
    },
    instant_search_price_asc: {
      refinementList: {
        brand: ["Apple"]
      }
    }
  }
});
```

Checkout the example on [Storybook](https://deploy-preview-4074--instantsearchjs.netlify.com/stories/?path=/story/instantsearch--with-initialuistate).
samouss added a commit that referenced this pull request Sep 17, 2019
**Summary**

This PR introduces the `initialUiState` option at the `InstantSearch` level. The option allows users to provide an `uiState` used for the initial request. Only for the **initial request**. I've chosen to name the option `initialXXX` rather than `uiState` to have a clear intent. At some point, we might want to introduce the concept of controlled state (like React InstantSearch) that could use the option `uiState`.

At the moment the type definitions are incorrect. We have only one type that defines the `uiState` but we have to define two different types. The first is one is the "global" `uiState` and the second one is the "local" to each index. The "global" `uiState` is built on multiple "local" `uiState`. We'll update the definition in a separate PR.


**Usage**

```js
const search = instantsearch({
  // ...
  initialUiState: {
    instant_search: {
      query: "Apple"
    },
    instant_search_price_asc: {
      refinementList: {
        brand: ["Apple"]
      }
    }
  }
});
```

Checkout the example on [Storybook](https://deploy-preview-4074--instantsearchjs.netlify.com/stories/?path=/story/instantsearch--with-initialuistate).
samouss added a commit that referenced this pull request Sep 17, 2019
**Summary**

This PR introduces the `initialUiState` option at the `InstantSearch` level. The option allows users to provide an `uiState` used for the initial request. Only for the **initial request**. I've chosen to name the option `initialXXX` rather than `uiState` to have a clear intent. At some point, we might want to introduce the concept of controlled state (like React InstantSearch) that could use the option `uiState`.

At the moment the type definitions are incorrect. We have only one type that defines the `uiState` but we have to define two different types. The first is one is the "global" `uiState` and the second one is the "local" to each index. The "global" `uiState` is built on multiple "local" `uiState`. We'll update the definition in a separate PR.


**Usage**

```js
const search = instantsearch({
  // ...
  initialUiState: {
    instant_search: {
      query: "Apple"
    },
    instant_search_price_asc: {
      refinementList: {
        brand: ["Apple"]
      }
    }
  }
});
```

Checkout the example on [Storybook](https://deploy-preview-4074--instantsearchjs.netlify.com/stories/?path=/story/instantsearch--with-initialuistate).
samouss added a commit that referenced this pull request Sep 17, 2019
**Summary**

This PR introduces the `initialUiState` option at the `InstantSearch` level. The option allows users to provide an `uiState` used for the initial request. Only for the **initial request**. I've chosen to name the option `initialXXX` rather than `uiState` to have a clear intent. At some point, we might want to introduce the concept of controlled state (like React InstantSearch) that could use the option `uiState`.

At the moment the type definitions are incorrect. We have only one type that defines the `uiState` but we have to define two different types. The first is one is the "global" `uiState` and the second one is the "local" to each index. The "global" `uiState` is built on multiple "local" `uiState`. We'll update the definition in a separate PR.


**Usage**

```js
const search = instantsearch({
  // ...
  initialUiState: {
    instant_search: {
      query: "Apple"
    },
    instant_search_price_asc: {
      refinementList: {
        brand: ["Apple"]
      }
    }
  }
});
```

Checkout the example on [Storybook](https://deploy-preview-4074--instantsearchjs.netlify.com/stories/?path=/story/instantsearch--with-initialuistate).
Haroenv pushed a commit that referenced this pull request Oct 23, 2019
**Summary**

This PR introduces the `initialUiState` option at the `InstantSearch` level. The option allows users to provide an `uiState` used for the initial request. Only for the **initial request**. I've chosen to name the option `initialXXX` rather than `uiState` to have a clear intent. At some point, we might want to introduce the concept of controlled state (like React InstantSearch) that could use the option `uiState`.

At the moment the type definitions are incorrect. We have only one type that defines the `uiState` but we have to define two different types. The first is one is the "global" `uiState` and the second one is the "local" to each index. The "global" `uiState` is built on multiple "local" `uiState`. We'll update the definition in a separate PR.


**Usage**

```js
const search = instantsearch({
  // ...
  initialUiState: {
    instant_search: {
      query: "Apple"
    },
    instant_search_price_asc: {
      refinementList: {
        brand: ["Apple"]
      }
    }
  }
});
```

Checkout the example on [Storybook](https://deploy-preview-4074--instantsearchjs.netlify.com/stories/?path=/story/instantsearch--with-initialuistate).
Haroenv added a commit that referenced this pull request Oct 23, 2019
* **configure:** merge with the previous parameters ([#4085](#4085)) ([a215d0c](a215d0c))
* **configure:** update lifecycle state ([#3994](#3994)) ([3d8d967](3d8d967))
* **connectInfiniteHits:** fix page state when adding or removing widgets ([#4104](#4104)) ([1077340](1077340))
* **connectInfiniteHits:** fix state when navigating or adding/removing widgets ([#4123](#4123)) ([9cbd24a](9cbd24a))
* **createURL:** support multi-index ([#4082](#4082)) ([179a6e5](179a6e5))
* **defer:** recover from error ([#3933](#3933)) ([f22b9e2](f22b9e2))
* **helper:** expose .lastResults to .helper ([#4170](#4170)) ([236eb7b](236eb7b))
* **history:** avoid empty query string ([#4130](#4130)) ([18fee7c](18fee7c))
* **hits:** update lifecycle state ([#3977](#3977)) ([6e55ba6](6e55ba6))
* **hitsPerPage:** avoid sync default value ([#4086](#4086)) ([3f8b958](3f8b958))
* **hitsPerPage:** update lifecycle state ([#3978](#3978)) ([d21d620](d21d620))
* **index:** ensure that we always use the index set by widgets ([#4125](#4125)) ([952dc70](952dc70)), closes [/github.com/algolia/algoliasearch-helper-js/blob/5a0352aa233c5ea932df6b054a16989c8d302404/src/algoliasearch.helper.js#L124](https://github.com//github.com/algolia/algoliasearch-helper-js/blob/5a0352aa233c5ea932df6b054a16989c8d302404/src/algoliasearch.helper.js/issues/L124)
* **index:** prevent render without results ([#3932](#3932)) ([1b9b5f4](1b9b5f4))
* **index:** subscribe to state change only after init for uiState ([#4003](#4003)) ([9490ca9](9490ca9))
* **index:** support custom UI params in UI state warning ([#4165](#4165)) ([80d32fc](80d32fc))
* **index:** warn for inconsistent UI state in development mode ([#4140](#4140)) ([7e277dc](7e277dc))
* **infiniteHits:** update lifecycle state ([#3983](#3983)) ([4b8bee5](4b8bee5))
* **instantsearch:** return instance in widgets methods ([#4143](#4143)) ([77ffb93](77ffb93))
* **InstantSearch:** cancel scheduled operations ([#3930](#3930)) ([3aafbad](3aafbad))
* **InstantSearch:** fix initialUIState when refinements are already present in the route ([#4103](#4103)) ([079db57](079db57))
* **InstantSearch:** remove useless walk/duplicate request ([#4127](#4127)) ([70163a8](70163a8))
* **menu:** apply & remove refinement ([#4027](#4027)) ([85de2cf](85de2cf))
* **menu:** prevent error on stale search ([#3934](#3934)) ([5f9e138](5f9e138))
* **numericMenu:** take array into account for empty state ([#4084](#4084)) ([2c05a01](2c05a01))
* **pagination:** update lifecycle state ([#3979](#3979)) ([2b08344](2b08344))
* **pagination:** update no refinement behavior ([#4124](#4124)) ([8d222ad](8d222ad))
* **range:** clear widget state on empty refinements ([#4157](#4157)) ([23cd112](23cd112))
* **ratingMenu:** update lifecycle state ([#3987](#3987)) ([ffadf64](ffadf64))
* **RefinementList:** remove root css class on sublists ([#4117](#4117)) ([ceddd42](ceddd42)), closes [/github.com/algolia/instantsearch.js/blob/v2/src/decorators/headerFooter.js#L22](https://github.com//github.com/algolia/instantsearch.js/blob/v2/src/decorators/headerFooter.js/issues/L22)
* **searchBox:** update lifecycle state ([#3981](#3981)) ([0ea4950](0ea4950))
* **sortBy:** ensure a return value for getWidgetSearchParameters ([#4126](#4126)) ([569d573](569d573))
* **sortBy:** read initial index name from parent index ([#4079](#4079)) ([fe23c55](fe23c55))
* display warnings only in development ([#4150](#4150)) ([44f69a0](44f69a0))
* remove useless types  ([#3958](#3958)) ([ddebf53](ddebf53))
* **stories:** hide Places ([#4152](#4152)) ([7ff843f](7ff843f))
* **toggleRefinement:** update lifecycle state ([#3993](#3993)) ([f1beff6](f1beff6))
* **voiceSearch:** update lifecycle state ([#3982](#3982)) ([798e3c1](798e3c1))
* **warnings:** remove v3 warnings ([#4134](#4134)) ([7eb6810](7eb6810))

* **autocomplete:** leverage scoped results ([#3975](#3975)) ([8f05968](8f05968))
* **autocomplete:** participate in routing ([#4029](#4029)) ([a9ca0c5](a9ca0c5))
* **autocomplete:** provide indexId ([#4142](#4142)) ([b641e23](b641e23))
* **clearRefinements:** support multiple indices ([#4036](#4036)) ([3611b11](3611b11))
* **connectAutocomplete:** add default value on getConfiguration ([#3836](#3836)) ([724b83f](724b83f))
* **connectAutocomplete:** clear the state on dispose ([#3815](#3815)) ([8ae87d8](8ae87d8))
* **connectHierarchicalMenu:** update getWidgetSearchParameters ([#4053](#4053)) ([c99f822](c99f822))
* **connectHits:** clear the state on dispose ([#3816](#3816)) ([c4de730](c4de730))
* **connectHits:** implement getWidgetSearchParameters ([#4001](#4001)) ([c77cf66](c77cf66))
* **connectHitsPerPage:** clear the state on dispose ([#3818](#3818)) ([d7a5c89](d7a5c89))
* **connectInfiniteHits:** add default value on getConfiguration ([#3837](#3837)) ([8c65249](8c65249))
* **connectInfiniteHits:** clear the state on dispose ([#3819](#3819)) ([60ce151](60ce151))
* **connectMenu:** update getWidgetSearchParameters ([#4054](#4054)) ([7d001e7](7d001e7))
* **connectNumericMenu:** update state lifecycle  ([#4013](#4013)) ([2620c90](2620c90))
* **connectPagination:** add default value on getConfiguration ([#3838](#3838)) ([aa4602c](aa4602c))
* **connectPagination:** clear the state on dispose ([#3821](#3821)) ([5b8ef49](5b8ef49))
* **connectPagination:** update getWidgetSearchParameters ([#4004](#4004)) ([eed7e77](eed7e77))
* **connectRange:** default `precision` to 0 ([#3953](#3953)) ([632e06b](632e06b))
* **connectRatingMenu:** update getWidgetSearchParameters  ([#4008](#4008)) ([d3c96bf](d3c96bf))
* **connectRefinementList:** update getWidgetSearchParameters  ([#4010](#4010)) ([ddc8fc4](ddc8fc4))
* **connectSearchBox:** clear the state on dispose ([#3822](#3822)) ([940522c](940522c))
* **connectSearchBox:** mount with a default query ([#3840](#3840)) ([c3a7d69](c3a7d69))
* **connectSearchBox:** update getWidgetSearchParameters ([#4002](#4002)) ([5c6fcd8](5c6fcd8))
* **connectVoiceSearch:** add default value on getConfiguration ([#3841](#3841)) ([fb70363](fb70363))
* **connectVoiceSearch:** clear the state on dispose ([#3823](#3823)) ([705b3e6](705b3e6))
* **connectVoiceSearch:** update getWidgetSearchParameters ([#4055](#4055)) ([b8c669f](b8c669f))
* **core:** deprecate addWidget & removeWidget ([#4131](#4131)) ([e5dafef](e5dafef))
* **currentRefinements:** support multiple indices ([#4012](#4012)) ([e997728](e997728))
* **defer:** implement cancellable callback ([#3916](#3916)) ([43a0bf8](43a0bf8))
* **federated:** keep a consistent state in the RefinementList life cycle ([#3976](#3976)) ([31d0fd6](31d0fd6))
* **hitsPerPage:** support new routing system ([#4038](#4038)) ([02502cb](02502cb)), closes [#4069](#4069)
* **index:** accept indexId ([#4070](#4070)) ([b74f8e3](b74f8e3))
* **index:** add mergeSearchParameters function ([#3917](#3917)) ([c0fe7bb](c0fe7bb))
* **index:** add widget ([dbbda0f](dbbda0f)), closes [#3892](#3892) [#3893](#3893) [#3914](#3914)
* **index:** compute local uiState ([#3997](#3997)) ([997c0f4](997c0f4))
* **index:** merge `ruleContexts` search parameter ([#3944](#3944)) ([e94752d](e94752d))
* **index:** provide scoped results to render hook ([#3964](#3964)) ([37c6aad](37c6aad))
* **index:** replicate searchFunction hack ([#4078](#4078)) ([1d2a816](1d2a816)), closes [/github.com/algolia/instantsearch.js/blob/509513c0feafaad522f6f18d87a441559f4aa050/src/lib/RoutingManager.ts#L113-L130](https://github.com//github.com/algolia/instantsearch.js/blob/509513c0feafaad522f6f18d87a441559f4aa050/src/lib/RoutingManager.ts/issues/L113-L130)
* **index:** reset page of child indexes ([#3962](#3962)) ([131b1ce](131b1ce))
* **index:** resolve parent SearchParameters ([#3937](#3937)) ([2611da5](2611da5))
* **index:** use uiState driven SearchParameters ([#4059](#4059)) ([b12bb9f](b12bb9f))
* **infiniteHits:** support new routing system ([#4040](#4040)) ([49315cf](49315cf))
* **instantsearch:** add onStateChange method ([#4080](#4080)) ([9f68da5](9f68da5))
* **InstantSearch:** switch to DerivedHelper only ([#3885](#3885)) ([d6fc317](d6fc317))
* **places:** add Places widget ([#4167](#4167)) ([1d754d1](1d754d1))
* drop support of searchParameters for initialUiState ([#4081](#4081)) ([571efeb](571efeb))
* **range:** support new routing system ([#4039](#4039)) ([8cba05a](8cba05a))
* **routing:** add a "single index" compatibility mode ([#4087](#4087)) ([842eb0f](842eb0f))
* **RoutingManager:** update state on route update ([#4100](#4100)) ([88f2615](88f2615))
* **toggleRefinement:** support new routing system ([#4037](#4037)) ([6a9d99f](6a9d99f))
* **types:** DerivedHelper ([#3887](#3887)) ([0f38b4a](0f38b4a))
* **types:** rename RenderOptions -> RendererOptions ([#3867](#3867)) ([05c6f72](05c6f72))
* **utils:** implement defer ([#3882](#3882)) ([8af470e](8af470e))
* **voice:** add additional query parameters ([#3738](#3738)) ([c555255](c555255))
* drop suppot for onHistoryChange ([#3941](#3941)) ([697f609](697f609))
* introduce initialUiState option ([#4074](#4074)) ([de00707](de00707))
* update UiState definition ([#4075](#4075)) ([9e7d3d8](9e7d3d8))
* **widgets:** add `$$type` to widgets definition ([#3960](#3960)) ([344d1b7](344d1b7))
@fabregas4
Copy link

Any evidence this actually works in v4? Can't get it to work at all myself. No console errors, but also zero impact on results. Have followed the example at https://www.algolia.com/doc/api-reference/widgets/instantsearch/js/#widget-param-initialuistate

@Haroenv
Copy link
Contributor

Haroenv commented Nov 11, 2019

Note that you need to mount a (could be virtual / non-rendering) widget too. We have a warning for that case normally, so I’d like to see your reproduction on codesandbox

@fabregas4
Copy link

@Haroenv Thanks for responding. Here is some code on codesandbox:
https://codesandbox.io/s/wizardly-jackson-ovx2f?fontsize=14

Not sure what you mean about mounting a widget. The hits widget should be enough right?

You'll see in the sample that there are results produced but they don't have the query or Designer refinement applied. Have tried various options but none are making a difference. Have tried just the query and not the refinementList, and vice versa. Not 100% sure if initialUiState is meant to have a property of "indexName" or "products" (which is the actual index name). Have tried both in any case. Have also tried not including this node, i.e. just:
initialUiState: { refinementList: { Designer: ["Zimmermann"] } }
which is how the code example says its done on this page: https://www.algolia.com/doc/guides/building-search-ui/upgrade-guides/js/#upgrade-from-v3-to-v4

I'm not the most advanced coder but the documentation in this area seems to be very light.

Shouldn't be any issue on the index side, as falling back to v3 and specifying searchParameters produces expected results. E.g.
searchParameters: { disjunctiveFacetsRefinements: {"Designer":["Zimmermann"]} }

Any ideas?

@Haroenv
Copy link
Contributor

Haroenv commented Nov 12, 2019

const search = instantsearch({
  indexName: "products",
  searchClient,
  initialUiState: {
    products: {
      query: "fakequerythatshouldreturnnoresults",
      refinementList: {
        Designer: ["Zimmermann"]
      }
    }
  }
});

const virtualRefinementList = instantsearch.connectors.connectRefinementList(
  () => null
);
const virtualSearchBox = instantsearch.connectors.connectSearchBox(
  () => null
);

search.addWidgets([
  virtualSearchBox(),
  virtualRefinementList({ attribute: "Designer" }),
  instantsearch.widgets.hits({
    container: "#hits",
    templates: {
      item: `<div>{{Designer}} - {{ProductTitle}}</div>`
    }
  })
]);

This is what's meant with "virtual widget".

Thanks for pointing out that this section is indeed insufficient to follow if you didn't already use advanced InstantSearch features.

Another option is the following:

search.addWidgets([
  instantsearch.widgets.configure(
      { disjunctiveFacetsRefinements: {"Designer":["Zimmermann"]} }
  ),
]);

@Haroenv
Copy link
Contributor

Haroenv commented Nov 12, 2019

see my example: https://codesandbox.io/s/determined-wood-quvoq

@fabregas4
Copy link

Thanks, that seems to have done the job. I must say it does seem more difficult than using searchParameters from previous versions, so I'm not sure why that would be deprecated in favour of this, although I'm sure you guys have your reasons :) Cheers

@Haroenv
Copy link
Contributor

Haroenv commented Nov 12, 2019

I think you missed the first part of that note, saying the default replacement is the configure widget. I've updated the docs to be clearer by the way

@fabregas4
Copy link

I did miss that, my bad. Looking at the docs now...

https://www.algolia.com/doc/api-reference/widgets/configure/js/
points me to:
https://www.algolia.com/doc/api-reference/search-api-parameters/

There is no mention of "disjunctiveFacetsRefinements" as a possible parameter in the searchParameters object.

@Haroenv
Copy link
Contributor

Haroenv commented Nov 12, 2019

True, that’s also somewhat confusing. The arguments aren’t just search parameters, but also helper ones: https://community.algolia.com/algoliasearch-helper-js/reference.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants