ToggleRefinement not working with routing URLs

Added a simple ToggleRefinement on this example https://codesandbox.io/embed/github/algolia/react-instantsearch/tree/master/examples/react-router

The search URL does not update after trying to toggle off the refinement and it refreshes to the same state.

Reproduced it in the code sandbox after happening locally.

Warning: Failed prop type: Invalid prop `currentRefinement` of type `string` supplied to `ToggleRefinement`, expected `boolean`.
    in ToggleRefinement (created by AlgoliaToggle(ToggleRefinement))
    in AlgoliaToggle(ToggleRefinement) (at Search/index.js:140)
    in div (at Search/index.js:126)
    in div (at Search/index.js:125)
    in div (at Search/index.js:122)
    in div (created by InstantSearch)
    in InstantSearch (created by CreateInstantSearch)
    in CreateInstantSearch (at Search/index.js:97)
    in div (at Search/index.js:96)
    in div (at Search/index.js:88)
    in Search (created by Connect(Search))
    in Connect(Search) (created by Route)
    in Route (at src/index.js:77)
    in Switch (at src/index.js:62)
    in div (at AppWrapper.js:128)
    in AppWrapper (created by Connect(AppWrapper))
    in Connect(AppWrapper) (created by Route)
    in Route (created by withRouter(Connect(AppWrapper)))
    in withRouter(Connect(AppWrapper)) (at src/index.js:60)
    in Router (at src/index.js:59)
    in Provider (at src/index.js:55)
    in StrictMode (at src/index.js:54)
console.<computed> @ index.js:1437
printWarning @ checkPropTypes.js:21
checkPropTypes @ checkPropTypes.js:76
validatePropTypes @ react.development.js:1716
createElementWithValidation @ react.development.js:1809
render @ createConnector.js:293
finishClassComponent @ react-dom.development.js:15320
updateClassComponent @ react-dom.development.js:15275
beginWork @ react-dom.development.js:16265
performUnitOfWork @ react-dom.development.js:20285
workLoop @ react-dom.development.js:20326
renderRoot @ react-dom.development.js:20406
performWorkOnRoot @ react-dom.development.js:21363
performWork @ react-dom.development.js:21273
performSyncWork @ react-dom.development.js:21247
requestWork @ react-dom.development.js:21102
scheduleWork @ react-dom.development.js:20915
enqueueSetState @ react-dom.development.js:11596
push../node_modules/react/cjs/react.development.js.Component.setState @ react.development.js:336
(anonymous) @ Router.js:102
listener @ history.js:192
(anonymous) @ history.js:210
notifyListeners @ history.js:209
setState @ history.js:330
(anonymous) @ history.js:411
confirmTransitionTo @ history.js:182
push @ history.js:392
(anonymous) @ index.js:62
setTimeout (async)
Search._this.onSearchStateChange @ index.js:61
onSearchStateChange @ InstantSearch.js:154
onWidgetsInternalStateUpdate @ InstantSearch.js:144
(anonymous) @ createConnector.js:94
onChange @ ToggleRefinement.js:22
callCallback @ react-dom.development.js:147
invokeGuardedCallbackDev @ react-dom.development.js:196
invokeGuardedCallback @ react-dom.development.js:250
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js:265
executeDispatch @ react-dom.development.js:571
executeDispatchesInOrder @ react-dom.development.js:596
executeDispatchesAndRelease @ react-dom.development.js:695
executeDispatchesAndReleaseTopLevel @ react-dom.development.js:704
forEachAccumulated @ react-dom.development.js:674
runEventsInBatch @ react-dom.development.js:844
runExtractedEventsInBatch @ react-dom.development.js:852
handleTopLevel @ react-dom.development.js:5030
batchedUpdates$1 @ react-dom.development.js:21469
batchedUpdates @ react-dom.development.js:2247
dispatchEvent @ react-dom.development.js:5110
(anonymous) @ react-dom.development.js:21526
unstable_runWithPriority @ scheduler.development.js:255
interactiveUpdates$1 @ react-dom.development.js:21525
interactiveUpdates @ react-dom.development.js:2268
dispatchInteractiveEvent @ react-dom.development.js:5086
index.js:1437 Warning: Received the string `true` for the boolean attribute `checked`. Although this works, it will not work as expected if you pass the string "false". Did you mean checked={true}?
    in input (created by ToggleRefinement)
    in label (created by ToggleRefinement)
    in div (created by ToggleRefinement)
    in ToggleRefinement (created by AlgoliaToggle(ToggleRefinement))
    in AlgoliaToggle(ToggleRefinement) (at Search/index.js:140)
    in div (at Search/index.js:126)
    in div (at Search/index.js:125)
    in div (at Search/index.js:122)
    in div (created by InstantSearch)
    in InstantSearch (created by CreateInstantSearch)
    in CreateInstantSearch (at Search/index.js:97)
    in div (at Search/index.js:96)
    in div (at Search/index.js:88)
    in Search (created by Connect(Search))
    in Connect(Search) (created by Route)
    in Route (at src/index.js:77)
    in Switch (at src/index.js:62)
    in div (at AppWrapper.js:128)
    in AppWrapper (created by Connect(AppWrapper))
    in Connect(AppWrapper) (created by Route)
    in Route (created by withRouter(Connect(AppWrapper)))
    in withRouter(Connect(AppWrapper)) (at src/index.js:60)
    in Router (at src/index.js:59)
    in Provider (at src/index.js:55)
    in StrictMode (at src/index.js:54)

it seems InstantSearch component is passing toggle value as string not boolean!

I solved it myself. The problem is in qs package, it parses boolean values as strings by default.

The official documentation should mention this.

This is the fix:
const urlToSearchState = ({ search }) => {
// console.log(search.slice(1));
const searchState = qs.parse(search.slice(1),
{ decoder: function (str, decoder, charset) {
const strWithoutPlus = str.replace(/+/g, ’ ');
if (charset === ‘iso-8859-1’) {
// unescape never throws, no try…catch needed:
return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape);
}

      if (/^(\d+|\d*\.\d+)$/.test(str)) {
        return parseFloat(str);
      }

      const keywords = {
        true: true,
        false: false,
        null: null,
        undefined,
      };
      if (str in keywords) {
        return keywords[str];
      }

      // utf-8
      try {
        return decodeURIComponent(strWithoutPlus);
      } catch (e) {
        return strWithoutPlus;
      }
    },
    }
  );
  if(searchState.toggle) {
    console.log('urltoss', searchState);
  }
  return searchState;
};

Thanks for the report, and the fix :smile:

We’ll see what we can do in the documentation or/and InstantSearch to avoid this kind of issue and make your life easier.