I have created a custom range facet, and I am having trouble right now syncing the URL parameters, and the state. It seems to work when on the page, and the user selects a single value in this facet. But if they select multiple values, there is no change. I am using this as one of my "disjunctiveFacets", in searchQuery. The field is called "field_publish_timestamp", and the goal for it is to allow users to select a year, and return results that have a date field corresponding to that year.
Another issue is when the user resubmits a keyword search from the search page during an existing search session. I'd expect it to preserve the facet selection, which I was able to do in my App.js file, using a custom onSubmit in my SearchBox that concatenates all the other query parameters onto a new URL with a replaced "q" parameter (or in my case "qu" as I had a parameter clash with the CMS I am using... You should think about namespacing such things).
This all lead me to believe that for this facet in particular, the app doesn't appear to be reading the filter values for this particular facet on page reload, despite successfully doing so for the other two (not range type) facets I have on the page.
Please bear with me, as I'm not very experienced with React. Here is "CustomDateRangeFacet":
import React from 'react';
import moment from 'moment';
import { useState } from 'react';
import {appendClassName, getFilterValueID} from "./helpers";
const CustomDateRangeFacet = ({
className,
label,
onMoreClick,
onRemove,
onSelect,
options,
showMore,
showSearch,
onSearch,
searchPlaceholder,
}) => {
const getRangeFromValue = (value) => {
switch (value) {
case 'all_time':
return { from: new Date('1970-01-01').toISOString(), to: new Date('2037-12-31').toISOString() };
case '2023':
return { from: new Date('2023-01-01').toISOString(), to: new Date('2023-12-31').toISOString() };
case '2022':
return { from: new Date('2022-01-01').toISOString(), to: new Date('2022-12-31').toISOString() };
case '2021':
return { from: new Date('2021-01-01').toISOString(), to: new Date('2021-12-31').toISOString() };
case 'before_2020':
return {from: new Date('1970-01-01').toISOString(), to: new Date('2019-12-31').toISOString() };
default:
return null;
}
};
const predefinedRanges = [
{ label: 'All time', value: 'all_time' },
{ label: '2023', value: '2023' },
{ label: '2022', value: '2022' },
{ label: '2021', value: '2021' },
{ label: 'Before 2020', value: 'before_2020' },
];
const handleCheckboxChange = (value, checked) => {
// Create a new Set with the current selected values
const newSelectedValues = new Set(selectedValues);
if (checked) {
newSelectedValues.add(value);
} else {
newSelectedValues.delete(value);
}
// Update the selected values state
setSelectedValues(newSelectedValues);
// Apply OR logic: combine all date ranges into a single filter
const selectedRanges = Array.from(newSelectedValues).map((selectedValue) => {
return getRangeFromValue(selectedValue);
});
const fromDates = selectedRanges
.map((range) => range && range.from)
.filter((from) => from !== undefined);
const toDates = selectedRanges
.map((range) => range && range.to)
.filter((to) => to !== undefined);
const minFrom = fromDates.length > 0 ? new Date(Math.min(...fromDates.map((from) => new Date(from)))).toISOString() : undefined;
const maxTo = toDates.length > 0 ? new Date(Math.max(...toDates.map((to) => new Date(to)))).toISOString() : undefined;
if (newSelectedValues.size > 0 && minFrom && maxTo) {
const combinedRange = {
from: isNaN(minFrom) ? new Date(minFrom).toISOString() : undefined,
to: isNaN(maxTo) ? new Date(maxTo).toISOString() : undefined,
};
if (checked) {
onSelect(combinedRange);
} else {
onRemove(combinedRange);
}
} else {
// Remove the filter completely when no ranges are selected
onRemove();
}
};
//console.log('options');
//console.log(options);
const [selectedValues, setSelectedValues] = useState(
new Set(options.filter((option) => option.selected).map((option) => option.value))
);
return (
<fieldset className={appendClassName("sui-facet", className)}>
<legend className="sui-facet__title">{label}</legend>
<div className="sui-multi-checkbox-facet">
{predefinedRanges.map((range, index) => {
const isChecked = options.some(
(option) => option.value === range.value && option.selected
);
console.log('selected Values:');
console.log(selectedValues);
console.log('range');
console.log(range);
console.log('isChecked');
console.log(isChecked);
return (
<label key={index}
htmlFor={`example_facet_${label}${getFilterValueID(range.value)}`}
className="sui-multi-checkbox-facet__option-label">
<div className="sui-multi-checkbox-facet__option-input-wrapper">
<input
data-transaction-name={`facet - ${label}`}
id={`example_facet_${label}${index}`}
type="checkbox"
className="sui-multi-checkbox-facet__checkbox"
checked={selectedValues.has(range.value)}
onChange={(e) => handleCheckboxChange(range.value, e.target.checked)}
/>
<span className="sui-multi-checkbox-facet__input-text">
{range.label}
</span>
</div>
<span className="sui-multi-checkbox-facet__option-count">
{options.count ? options.count.toLocaleString('en') : ''}
</span>
</label>
);
})}
</div>
</fieldset>
);
};
export default CustomDateRangeFacet;
Another issue I noticed is that in the query parameters, it seems to use: "&filters[2][type]=all
" which I assume is incorrect, as I think the "type" should be "range". I added this facet definition directly in my App.js file, though it doesn't seem to be making any difference.
facets: {
...buildFacetConfigFromConfig(),
field_publish_timestamp: {
type: "range",
ranges: [
{ from: "1900-01-01T00:00:00.000Z", to: "2045-12-31T00:00:00.000Z", name: 'All time' },
{ from: "2023-01-01T00:00:00.000Z", to: "2023-12-31T00:00:00.000Z", name: '2023' },
{ from: "2022-01-01T00:00:00.000Z", to: "2022-12-31T00:00:00.000Z", name: '2022' },
{ from: "2021-01-01T00:00:00.000Z", to: "2021-12-31T00:00:00.000Z", name: '2023' },
{ from: "1900-01-01T00:00:00.000Z", to: "2020-12-31T00:00:00.000Z", name: 'Before 2020' },
]
}
},
One more question, are there any more simple examples for a search app than what is in the documentation? The documentation appears to dive into using Vue and a bunch of other things, which for me feels like running before I've learned to walk here.