Suppose there's an attribute filter which has a defaultRefinement.
Then this attribute filter refinement is changed via refine() when a user clicks some button.
After that the user clicks another button (say, "reset") which should reset the filter to its initial state.
refine(defaultRefinement) won't work because it would reset to some constant values while the original defaultRefinement behaviour is to constantly re-evaluate the defaultRefinement.
E.g. a defaultRefinement is { min: Date.now() }.
Then it is re-evaluated each time filters change (the "min" filter being sent to the server is different each time filters are changed).
If it was a strict refine({ min: Date.now() }) then it would stay the same.
I checked it in the Network tab.
So, how to reset a filter to defaultRefinement behaviour after being refine()d?
Right now there is no built-in way to reset to the defaultRefinement. Once a user refines the value the reset operation will remove the refinement without re-applying the defaultRefinement.
The value is not computed every time, but it's re-computed each time a parent component will render since the value is inlined inside the render. To solve this use case you can persist the default value somewhere (class instance, state, whatever it's not important). The you can provide this persisted value to the defaultRefinement. Now refine(defaultRefinement) will always be the same value.
@samouss
To solve this use case you can persist the default value somewhere (class instance, state, whatever it's not important). The you can provide this persisted value to the defaultRefinement. Now refine(defaultRefinement) will always be the same value.
But that's the contrary: we do find this defaultRefinement re-evaluation feature behaving as expected.
Consider there's a toggler called "Show Past Events" and a defaultRefinement of { min: Date.now() }.
Now, this works as expected because as the user narrows down the search or re-adjusts the filters the "Show Past Events" filter does hide the events which already ended.
Consider a browser tab which stays open for days, or even weeks, which is how I do things, for example.
A user toggles "Show Past Events" toggler and that discards the defaultRefinement auto-update feature.
In this scenario, the user will be very confused to see events that have already expired (for days) after re-adjusting the filters.
You're saying that there's no way to implement "Show Past Events" toggler (filter) so that it behaves correctly.
Or is there?
I'm sorry but I struggle a bit to understand the behaviour you want. What is the behaviour that you expect at the end? You can create an example if it helps to understand. Here is a template on CodeSandbox.
I'm sorry but I struggle a bit to understand the behaviour you want.
Suppose there's a website which uses Algolia search component for outputting a list of events (e.g. festivals).
Most visitors would only be interested in upcoming events.
So the website adds a custom search widget which is a toggle saying "Including past events".
By default this toggle is "off" which means that date attribute is filtered by defaultRefinement={{ min: Date.now() }}.
When a user toggles the toggle to "on" the widget calls refine({ min: undefined, max: undefined }) which resets the defaultRefinement behaviour and you're saying that there's no way to undo this operation.
So, after the user has toggled the toggle to "on" he decides that he clicked it by accident or whatever and toggles it to "off".
Since you're saying there's no way to re-apply the defaultRefinement behaviour, it just does refine({ min: Date.now() }) instead.
But that's not the same as the defaultRefinement it used to be because now the min is a fixed point in time and, say, when the user goes to have a cup of tea and then comes back after 15 minutes (or half an hour, after a lunch) and changes the search criteria (e.g. query) then he'll still be getting stale events which already finsihed which would confuse the user and he would possibly not buy a ticket, while with the defaultRefinement untouched it would always re-evaluate defaultRefinement={{ min: Date.now() }} when changing search criteria and would never show stale results.
What is the behaviour that you expect at the end?
The ability to reset to defaultRefinement as if it was untouched.
Thanks for the explanations! I think I got it now: basically the search should always be performed with Date.now() unless the user include the past events right? In that case I don't think you can use the defaultRefinement because indeed it's not re-evaluated on each request but only on a new render of the parent component.
You can create a connector that solve this use case though. The connector will compute the min from Date.now() on each requests unless the user specify to include past events. Here is an example that shows how to implement it. Once the checkbox is checked the price will be a random value that change each time we trigger a search (look at the numericFilters inside the Network tab). You can implement a similar behaviour for the date.
@samouss
Ha, that looks interesting.
I guess you're right after all: defaultRefinement is not suited for such tasks, and implementing a custom "connector" would be the right way.
Just didn't want to dive too deep into your sources.
Seems that this case though is simple enough, and even simpler than hacking around as I did previously.
And the searchParameters object seems to be the "algoliasearch-helper".
https://github.com/algolia/algoliasearch-helper-js
Ok then, I'm closing this issue, thanks for pointing us in the right direction.
I'm putting the "Show Past" / "Hide Past" widget connector code here for anyone searching for it.
It's not guaranteed to be bug-free.
Seems to work.
import { createConnector } from 'react-instantsearch';
export default createConnector({
displayName: 'InstantSearchExcludePastConnector',
getProvidedProps(props, searchState) {
return {
currentRefinement: getCurrentRefinement(searchState, props)
};
},
refine(props, searchState, nextRefinement) {
return {
...searchState,
excludePast: {
[props.attributeName]: isDefault(nextRefinement, props) ? undefined : nextRefinement
}
};
},
cleanUp(props, searchState) {
return {
...searchState,
excludePast: {
...searchState.excludePast,
[props.attributeName]: undefined
}
}
},
getSearchParameters(searchParameters, props, searchState) {
if (getCurrentRefinement(searchState, props)) {
const { now } = props;
return searchParameters.addNumericRefinement(
props.attributeName,
'>',
now
);
}
return searchParameters;
},
getMetadata(props, searchState) {
const items = [];
const isNonDefault = searchState.excludePast && !isDefault(searchState.excludePast[props.attributeName], props);
if (isNonDefault) {
items.push({
label: props.clearLabel || props.label,
attribute: props.attributeName,
value: (nextState) => ({
...nextState,
excludePast: {
...nextState.excludePast,
[props.attributeName]: undefined
}
}),
currentRefinement: getCurrentRefinement(searchState, props)
});
}
return {
id: props.attributeName,
index: getIndex(this.context),
items
};
}
});
function isDefault(value, props) {
if (value === undefined) {
return true;
}
const defaultRefinement = props.defaultRefinement === undefined ? false : props.defaultRefinement;
return value === defaultRefinement
}
function getIndex(context) {
return context && context.multiIndexContext
? context.multiIndexContext.targetedIndex
: context.ais.mainTargetedIndex;
}
function getCurrentRefinement(searchState, props) {
const excludePast = searchState.excludePast;
return !excludePast || excludePast[props.attributeName] === undefined ? props.defaultRefinement : excludePast[props.attributeName];
}
Most helpful comment
Thanks for the explanations! I think I got it now: basically the search should always be performed with
Date.now()unless the user include the past events right? In that case I don't think you can use thedefaultRefinementbecause indeed it's not re-evaluated on each request but only on a new render of the parent component.You can create a connector that solve this use case though. The connector will compute the min from
Date.now()on each requests unless the user specify to include past events. Here is an example that shows how to implement it. Once the checkbox is checked the price will be a random value that change each time we trigger a search (look at thenumericFiltersinside the Network tab). You can implement a similar behaviour for the date.