diff --git a/src/js/components/Beacon/BeaconQueryUi.tsx b/src/js/components/Beacon/BeaconQueryUi.tsx index 04d4d13e..a0d28d82 100644 --- a/src/js/components/Beacon/BeaconQueryUi.tsx +++ b/src/js/components/Beacon/BeaconQueryUi.tsx @@ -1,15 +1,9 @@ import type { ReactNode } from 'react'; import { useEffect, useState, useCallback, useMemo } from 'react'; -import { useAppSelector, useAppDispatch, useTranslationDefault, useQueryWithAuthIfAllowed } from '@/hooks'; import { Button, Card, Col, Form, Row, Space, Tooltip, Typography } from 'antd'; import { InfoCircleOutlined } from '@ant-design/icons'; import { useIsAuthenticated } from 'bento-auth-js'; -import Filters from './Filters'; -import BeaconSearchResults from './BeaconSearchResults'; -import BeaconErrorMessage from './BeaconErrorMessage'; -import VariantsForm from './VariantsForm'; -import { makeBeaconQuery } from '@/features/beacon/beaconQuery.store'; -import type { BeaconQueryPayload, FormFilter, FormValues, PayloadFilter, PayloadVariantsQuery } from '@/types/beacon'; + import { WRAPPER_STYLE, FORM_ROW_GUTTERS, @@ -18,9 +12,18 @@ import { BUTTON_STYLE, CARD_STYLES, } from '@/constants/beaconConstants'; - import { BOX_SHADOW } from '@/constants/overviewConstants'; +import { makeBeaconQuery } from '@/features/beacon/beaconQuery.store'; +import { useSearchQuery } from '@/features/search/hooks'; +import { useAppSelector, useAppDispatch, useTranslationDefault, useQueryWithAuthIfAllowed } from '@/hooks'; +import type { BeaconQueryPayload, FormFilter, FormValues, PayloadFilter, PayloadVariantsQuery } from '@/types/beacon'; + import Loader from '@/components/Loader'; +import Filters from './Filters'; +import BeaconSearchResults from './BeaconSearchResults'; +import BeaconErrorMessage from './BeaconErrorMessage'; +import VariantsForm from './VariantsForm'; + const { Text, Title } = Typography; // TODOs // example searches, either hardcoded or configurable @@ -58,7 +61,7 @@ const BeaconQueryUi = () => { const isFetchingBeaconConfig = useAppSelector((state) => state.beaconConfig.isFetchingBeaconConfig); const beaconAssemblyIds = useAppSelector((state) => state.beaconConfig.beaconAssemblyIds); - const querySections = useAppSelector((state) => state.query.querySections); + const { querySections } = useSearchQuery(); const hasApiError = useAppSelector((state) => state.beaconQuery.hasApiError); const apiErrorMessage = useAppSelector((state) => state.beaconQuery.apiErrorMessage); diff --git a/src/js/components/Search/MakeQueryOption.tsx b/src/js/components/Search/MakeQueryOption.tsx index 5d44836b..731d1469 100644 --- a/src/js/components/Search/MakeQueryOption.tsx +++ b/src/js/components/Search/MakeQueryOption.tsx @@ -1,12 +1,13 @@ import { useCallback } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; import { Row, Col, Checkbox } from 'antd'; import OptionDescription from './OptionDescription'; import SelectOption from './SelectOption'; +import { useSearchQuery } from '@/features/search/hooks'; import { useAppSelector, useTranslationCustom, useTranslationDefault } from '@/hooks'; import type { Field } from '@/types/search'; -import { useLocation, useNavigate } from 'react-router-dom'; import { buildQueryParamsUrl, queryParamsWithoutKey } from '@/utils/search'; const MakeQueryOption = ({ queryField }: MakeQueryOptionProps) => { @@ -19,7 +20,7 @@ const MakeQueryOption = ({ queryField }: MakeQueryOptionProps) => { const { title, id, description, config, options } = queryField; const { maxQueryParameters } = useAppSelector((state) => state.config); - const { queryParamCount, queryParams } = useAppSelector((state) => state.query); + const { queryParamCount, queryParams } = useSearchQuery(); const isChecked = id in queryParams; diff --git a/src/js/components/Search/Search.tsx b/src/js/components/Search/Search.tsx index 08b0aa61..5593716b 100644 --- a/src/js/components/Search/Search.tsx +++ b/src/js/components/Search/Search.tsx @@ -9,8 +9,9 @@ import { makeGetKatsuPublic, setQueryParams } from '@/features/search/query.stor import { useAppDispatch, useAppSelector, useTranslationCustom } from '@/hooks'; import { buildQueryParamsUrl } from '@/utils/search'; -import type { QueryParams } from '@/types/search'; import Loader from '@/components/Loader'; +import { useSearchQuery } from '@/features/search/hooks'; +import type { QueryParams } from '@/types/search'; const checkQueryParamsEqual = (qp1: QueryParams, qp2: QueryParams): boolean => { const qp1Keys = Object.keys(qp1); @@ -32,7 +33,7 @@ const RoutedSearch = () => { isFetchingData: isFetchingSearchData, attemptedFieldsFetch, attemptedFetch, - } = useAppSelector((state) => state.query); + } = useSearchQuery(); // TODO: allow disabling max query parameters for authenticated and authorized users when Katsu has AuthZ // const maxQueryParametersRequired = useAppSelector((state) => state.config.maxQueryParametersRequired); @@ -117,9 +118,7 @@ const SEARCH_SECTION_STYLE = { maxWidth: 1200 }; const Search = () => { const t = useTranslationCustom(); - const { isFetchingFields: isFetchingSearchFields, querySections: searchSections } = useAppSelector( - (state) => state.query - ); + const { isFetchingFields: isFetchingSearchFields, querySections: searchSections } = useSearchQuery(); return isFetchingSearchFields ? ( diff --git a/src/js/components/Search/SearchResults.tsx b/src/js/components/Search/SearchResults.tsx index 8bee4559..5ae84d99 100644 --- a/src/js/components/Search/SearchResults.tsx +++ b/src/js/components/Search/SearchResults.tsx @@ -1,15 +1,17 @@ -import { useAppSelector } from '@/hooks'; +import { useSearchQuery } from '@/features/search/hooks'; import SearchResultsPane from './SearchResultsPane'; const SearchResults = () => { - const isFetchingData = useAppSelector((state) => state.query.isFetchingData); - const biosampleCount = useAppSelector((state) => state.query.biosampleCount); - const biosampleChartData = useAppSelector((state) => state.query.biosampleChartData); - const experimentCount = useAppSelector((state) => state.query.experimentCount); - const experimentChartData = useAppSelector((state) => state.query.experimentChartData); - const individualCount = useAppSelector((state) => state.query.individualCount); - const individualMatches = useAppSelector((state) => state.query.individualMatches); - const message = useAppSelector((state) => state.query.message); + const { + isFetchingData, + biosampleCount, + biosampleChartData, + experimentCount, + experimentChartData, + individualCount, + individualMatches, + message, + } = useSearchQuery(); // existing code treats non-empty message as sign of insufficient data const hasInsufficientData = message !== ''; diff --git a/src/js/components/Search/SelectOption.tsx b/src/js/components/Search/SelectOption.tsx index e700dbc0..f1ce9dc5 100644 --- a/src/js/components/Search/SelectOption.tsx +++ b/src/js/components/Search/SelectOption.tsx @@ -3,7 +3,8 @@ import { useCallback, useMemo } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { Select } from 'antd'; -import { useAppSelector, useTranslationCustom } from '@/hooks'; +import { useTranslationCustom } from '@/hooks'; +import { useSearchQuery } from '@/features/search/hooks'; import { buildQueryParamsUrl } from '@/utils/search'; const SELECT_STYLE: CSSProperties = { width: '100%' }; @@ -14,7 +15,7 @@ const SelectOption = ({ id, isChecked, options }: SelectOptionProps) => { const { pathname } = useLocation(); const navigate = useNavigate(); - const { queryParams } = useAppSelector((state) => state.query); + const { queryParams } = useSearchQuery(); const defaultValue = queryParams[id] || options[0]; const handleValueChange = useCallback( diff --git a/src/js/components/SiteSider.tsx b/src/js/components/SiteSider.tsx index a1b7e61c..aac2a4a3 100644 --- a/src/js/components/SiteSider.tsx +++ b/src/js/components/SiteSider.tsx @@ -7,10 +7,11 @@ import type { MenuProps, SiderProps } from 'antd'; import Icon, { PieChartOutlined, SearchOutlined, SolutionOutlined } from '@ant-design/icons'; import BeaconSvg from '@/components/Beacon/BeaconSvg'; -import { useAppSelector, useTranslationDefault } from '@/hooks'; +import { useSearchQuery } from '@/features/search/hooks'; +import { useTranslationDefault } from '@/hooks'; +import { BentoRoute } from '@/types/routes'; import { buildQueryParamsUrl } from '@/utils/search'; import { getCurrentPage } from '@/utils/router'; -import { BentoRoute } from '@/types/routes'; const { Sider } = Layout; @@ -27,7 +28,7 @@ const SiteSider: React.FC<{ const navigate = useNavigate(); const location = useLocation(); const td = useTranslationDefault(); - const queryParams = useAppSelector((state) => state.query.queryParams); + const { queryParams } = useSearchQuery(); const currentPage = getCurrentPage(); const handleMenuClick: OnClick = useCallback( diff --git a/src/js/features/search/hooks.ts b/src/js/features/search/hooks.ts new file mode 100644 index 00000000..af6b20e0 --- /dev/null +++ b/src/js/features/search/hooks.ts @@ -0,0 +1,3 @@ +import { useAppSelector } from '@/hooks'; + +export const useSearchQuery = () => useAppSelector((state) => state.query);