ToggleRefinement Routing and Booleans: Facet "true" vs widget toggle status "true"

Hey friends!

I have a basic toggleRefinement widget using vanilla InstantSearch.js. When the toggle is switched on, only results where the facet value is false should be shown. Using the e-commerce example terminology, I’m looking to create a toggle to “exclude free shipping”.

Pretty straightforward - I set the on parameter in the widget to false.

However, when the toggle is switched on, free_shipping=true is what gets sent to the routers routeToState. I would expect free_shipping=false. It appears InstantSearch is saying the “widget toggle state is true”, instead of “the facet value is false”?

Here’s a basic reproduction using the default InstantSearch v4 CodeSandbox. Note how the URL shows free_shipping%5D=true, despite the results only showing results where free_shipping=false.

Perhaps I’m missing something super simple, but if that is indeed the expected behavior, I’d love some suggestions for how best to flip the boolean in the router.

The alternative I suppose is to adjust my records as appropriate, e.g. paid_shipping, but free_shipping just feels so much nicer.

Thanks!

Hi @chad,

The query parameter is indicating whether the checkbox in the refinement is checked or not, not the value passed in the refinement. The ‘on’ option indicates what the checked value means, not whether the checkbox is checked or not.

If you change the value in the query parameter, you will need to make some customizations to switch the value so that the checkbox will be checked instead of unchecked.

Thanks for confirming that is indeed the expected behavior @cindy.cullen (i.e. router uses the “toggle state” instead of the facet value).

In case others find themselves with a similar use case, here are two possible solutions (I also updated the CodeSandbox - see link above).

Note: I haven’t fully tested these for unintended side effects.

Option 1: Rename the facet

routing: {
    stateMapping: {
      stateToRoute(uiState) {
        const indexUiState = uiState['instant_search'];
        return {
          query: indexUiState.query,
          brand:
            indexUiState.refinementList &&
            indexUiState.refinementList.brand &&
            indexUiState.refinementList.brand.join('~'),
          // Rename toggle for clarity
          exclude_free_shipping:
            indexUiState.toggle &&
            indexUiState.toggle.free_shipping,
          page: indexUiState.page,
        };
      },
      routeToState(routeState) {
        return {
          instant_search: {
            query: routeState.query,
            refinementList: {
              brand: routeState.brand && routeState.brand.split('~'),
            },
            toggle: {
              free_shipping: routeState.exclude_free_shipping,
            },
            page: routeState.page,
          },
        };
      },
    },
  },

Option 2: Flip the boolean

routing: {
    stateMapping: {
      stateToRoute(uiState) {
        const indexUiState = uiState['instant_search'];
        return {
          query: indexUiState.query,
          brand:
            indexUiState.refinementList &&
            indexUiState.refinementList.brand &&
            indexUiState.refinementList.brand.join('~'),
          free_shipping:
            indexUiState.toggle &&
            // Flip boolean to improve UX
            !indexUiState.toggle.free_shipping,
          page: indexUiState.page,
        };
      },
      routeToState(routeState) {
        return {
          instant_search: {
            query: routeState.query,
            refinementList: {
              brand: routeState.brand && routeState.brand.split('~'),
            },
            toggle: {
              free_shipping: routeState.free_shipping,
            },
            page: routeState.page,
          },
        };
      },
    },
  },
1 Like