Syncing facet states and URL for range facet

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.

I also think it's odd the search field forces a page reload when there is a new search query. But I might be breaking that with this in my SearchBox?

onSubmit={(searchTerm) => {
  window.location.href = createSearchURL(searchTerm);
}}

So I just threw out what I had and started fresh based on the example from search-ui/MultiCheckboxFacet.tsx at main · elastic/search-ui · GitHub and I don't seem to have any issues copying from the URL and state anymore.

I am running into a different issue though, despite my facets being classified as "Disjunctive facets" so they use OR logic, they still seem to use AND logic, and selecting multiple values eliminates all results entirely. This isn't ideal for me, as my facets are mutually exclusive, e.g. there can be only one publish date, or "type". Any tips on this?

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.