Experimental filters: fix providers issues (#3931)

* feat: fast fixes

* fix: cleanup code

* chore: add changeset
This commit is contained in:
Krzysztof Żuraw 2023-07-14 11:01:57 +02:00 committed by GitHub
parent d2faa47001
commit 423858e86f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 180 additions and 196 deletions

View file

@ -0,0 +1,5 @@
---
"saleor-dashboard": patch
---
Experimental filters: fix providers issues

View file

@ -1,8 +1,6 @@
import { FilterContainer } from "../FilterElement"; import { FilterContainer } from "../FilterElement";
import { ItemOption } from "../FilterElement/ConditionValue"; import { ItemOption } from "../FilterElement/ConditionValue";
import { LeftOperand } from "../LeftOperandsProvider"; import { LeftOperand } from "../LeftOperandsProvider";
import { FetchingParams } from "../ValueProvider/TokenArray/fetchingParams";
import { InitialStateResponse } from "./InitialStateResponse";
export interface FilterAPIProvider { export interface FilterAPIProvider {
fetchRightOptions: ( fetchRightOptions: (
@ -11,8 +9,4 @@ export interface FilterAPIProvider {
inputValue: string, inputValue: string,
) => Promise<ItemOption[]>; ) => Promise<ItemOption[]>;
fetchLeftOptions: (inputValue: string) => Promise<LeftOperand[]>; fetchLeftOptions: (inputValue: string) => Promise<LeftOperand[]>;
useInitialState: (fetchingParams: FetchingParams) => {
data: InitialStateResponse;
loading: boolean;
};
} }

View file

@ -31,6 +31,10 @@ export class InitialStateResponse implements InitialState {
return this.attribute[name]; return this.attribute[name];
} }
public static empty() {
return new InitialStateResponse([], {}, [], [], []);
}
public filterByUrlToken(token: UrlToken) { public filterByUrlToken(token: UrlToken) {
if (token.isAttribute()) { if (token.isAttribute()) {
return this.attribute[token.name].choices.filter(({ value }) => return this.attribute[token.name].choices.filter(({ value }) =>

View file

@ -1,7 +1,6 @@
import { ApolloClient, useApolloClient } from "@apollo/client"; import { ApolloClient, useApolloClient } from "@apollo/client";
import { FilterContainer, FilterElement } from "../FilterElement"; import { FilterContainer, FilterElement } from "../FilterElement";
import { FetchingParams } from "../ValueProvider/TokenArray/fetchingParams";
import { FilterAPIProvider } from "./FilterAPIProvider"; import { FilterAPIProvider } from "./FilterAPIProvider";
import { import {
AttributeChoicesHandler, AttributeChoicesHandler,
@ -12,11 +11,6 @@ import {
Handler, Handler,
ProductTypeHandler, ProductTypeHandler,
} from "./Handler"; } from "./Handler";
import {
createInitialStateFromData,
useDataFromAPI,
} from "./initialState/helpers";
import { InitialStateResponse } from "./InitialStateResponse";
const getFilterElement = ( const getFilterElement = (
value: FilterContainer, value: FilterContainer,
@ -85,31 +79,8 @@ export const useProductFilterAPIProvider = (): FilterAPIProvider => {
return handler.fetch(); return handler.fetch();
}; };
const useInitialState = (fetchingParams: FetchingParams) => {
const { data, loading } = useDataFromAPI({
...fetchingParams,
});
const initialState = createInitialStateFromData(
data,
fetchingParams.channel,
);
return {
data: new InitialStateResponse(
initialState.category,
initialState.attribute,
initialState.channel,
initialState.collection,
initialState.producttype,
),
loading,
};
};
return { return {
fetchRightOptions, fetchRightOptions,
fetchLeftOptions, fetchLeftOptions,
useInitialState,
}; };
}; };

View file

@ -1,24 +1,12 @@
import { ApolloQueryResult, useApolloClient } from "@apollo/client"; import { ApolloQueryResult } from "@apollo/client";
import { import {
_GetChannelOperandsDocument,
_GetChannelOperandsQuery, _GetChannelOperandsQuery,
_GetChannelOperandsQueryVariables,
_SearchAttributeOperandsDocument,
_SearchAttributeOperandsQuery, _SearchAttributeOperandsQuery,
_SearchAttributeOperandsQueryVariables,
_SearchCategoriesOperandsDocument,
_SearchCategoriesOperandsQuery, _SearchCategoriesOperandsQuery,
_SearchCategoriesOperandsQueryVariables,
_SearchCollectionsOperandsDocument,
_SearchCollectionsOperandsQuery, _SearchCollectionsOperandsQuery,
_SearchCollectionsOperandsQueryVariables,
_SearchProductTypesOperandsDocument,
_SearchProductTypesOperandsQuery, _SearchProductTypesOperandsQuery,
_SearchProductTypesOperandsQueryVariables,
} from "@dashboard/graphql"; } from "@dashboard/graphql";
import { useEffect, useState } from "react";
import { FetchingParams } from "../../ValueProvider/TokenArray/fetchingParams";
import { createOptionsFromAPI } from "../Handler"; import { createOptionsFromAPI } from "../Handler";
import { InitialState } from "../InitialStateResponse"; import { InitialState } from "../InitialStateResponse";
import { InitialAPIResponse } from "./types"; import { InitialAPIResponse } from "./types";
@ -118,104 +106,3 @@ export const createInitialStateFromData = (
attribute: {}, attribute: {},
}, },
); );
export const useDataFromAPI = ({
category,
collection,
producttype,
channel,
attribute,
}: FetchingParams) => {
const client = useApolloClient();
const [data, setData] = useState<InitialAPIResponse[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const queriesToRun: Array<Promise<InitialAPIResponse>> = [];
const fetchQueries = async () => {
const data = await Promise.all(queriesToRun);
setData(data);
setLoading(false);
};
if (channel.length > 0) {
queriesToRun.push(
client.query<
_GetChannelOperandsQuery,
_GetChannelOperandsQueryVariables
>({
query: _GetChannelOperandsDocument,
}),
);
}
if (collection.length > 0) {
queriesToRun.push(
client.query<
_SearchCollectionsOperandsQuery,
_SearchCollectionsOperandsQueryVariables
>({
query: _SearchCollectionsOperandsDocument,
variables: {
collectionsSlugs: collection,
first: collection.length,
},
}),
);
}
if (category.length > 0) {
queriesToRun.push(
client.query<
_SearchCategoriesOperandsQuery,
_SearchCategoriesOperandsQueryVariables
>({
query: _SearchCategoriesOperandsDocument,
variables: {
categoriesSlugs: category,
first: category.length,
},
}),
);
}
if (producttype.length > 0) {
queriesToRun.push(
client.query<
_SearchProductTypesOperandsQuery,
_SearchProductTypesOperandsQueryVariables
>({
query: _SearchProductTypesOperandsDocument,
variables: {
productTypesSlugs: producttype,
first: producttype.length,
},
}),
);
}
if (Object.keys(attribute).length > 0) {
queriesToRun.push(
client.query<
_SearchAttributeOperandsQuery,
_SearchAttributeOperandsQueryVariables
>({
query: _SearchAttributeOperandsDocument,
variables: {
attributesSlugs: Object.keys(attribute),
choicesIds: Object.values<string[]>(attribute).flat(),
first: Object.keys(attribute).length,
},
}),
);
}
void fetchQueries();
}, []);
return {
data,
loading,
};
};

View file

@ -1 +1 @@
export * from "./useInitalAPIState"; export * from "./useInitialAPIState";

View file

@ -1,29 +0,0 @@
import { FetchingParams } from "../../ValueProvider/TokenArray/fetchingParams";
import { InitialStateResponse } from "../InitialStateResponse";
import { createInitialStateFromData, useDataFromAPI } from "./helpers";
export interface InitialStateAPIProvider {
data: InitialStateResponse;
loading: boolean;
}
export const useInitialAPIState = (
props: FetchingParams,
): InitialStateAPIProvider => {
const { data, loading } = useDataFromAPI({
...props,
});
const initialState = createInitialStateFromData(data, props.channel);
return {
data: new InitialStateResponse(
initialState.category,
initialState.attribute,
initialState.channel,
initialState.collection,
initialState.producttype,
),
loading,
};
};

View file

@ -0,0 +1,138 @@
import { useApolloClient } from "@apollo/client";
import {
_GetChannelOperandsDocument,
_GetChannelOperandsQuery,
_GetChannelOperandsQueryVariables,
_SearchAttributeOperandsDocument,
_SearchAttributeOperandsQuery,
_SearchAttributeOperandsQueryVariables,
_SearchCategoriesOperandsDocument,
_SearchCategoriesOperandsQuery,
_SearchCategoriesOperandsQueryVariables,
_SearchCollectionsOperandsDocument,
_SearchCollectionsOperandsQuery,
_SearchCollectionsOperandsQueryVariables,
_SearchProductTypesOperandsDocument,
_SearchProductTypesOperandsQuery,
_SearchProductTypesOperandsQueryVariables,
} from "@dashboard/graphql";
import { useState } from "react";
import { FetchingParams } from "../../ValueProvider/TokenArray/fetchingParams";
import { InitialStateResponse } from "../InitialStateResponse";
import { createInitialStateFromData } from "./helpers";
import { InitialAPIResponse } from "./types";
export interface InitialAPIState {
data: InitialStateResponse;
loading: boolean;
fetchQueries: (params: FetchingParams) => void;
}
export const useProductInitialAPIState = (): InitialAPIState => {
const client = useApolloClient();
const [data, setData] = useState<InitialStateResponse>(
InitialStateResponse.empty(),
);
const [loading, setLoading] = useState(true);
const queriesToRun: Array<Promise<InitialAPIResponse>> = [];
const fetchQueries = async ({
category,
collection,
producttype,
channel,
attribute,
}: FetchingParams) => {
if (channel.length > 0) {
queriesToRun.push(
client.query<
_GetChannelOperandsQuery,
_GetChannelOperandsQueryVariables
>({
query: _GetChannelOperandsDocument,
}),
);
}
if (collection.length > 0) {
queriesToRun.push(
client.query<
_SearchCollectionsOperandsQuery,
_SearchCollectionsOperandsQueryVariables
>({
query: _SearchCollectionsOperandsDocument,
variables: {
collectionsSlugs: collection,
first: collection.length,
},
}),
);
}
if (category.length > 0) {
queriesToRun.push(
client.query<
_SearchCategoriesOperandsQuery,
_SearchCategoriesOperandsQueryVariables
>({
query: _SearchCategoriesOperandsDocument,
variables: {
categoriesSlugs: category,
first: category.length,
},
}),
);
}
if (producttype.length > 0) {
queriesToRun.push(
client.query<
_SearchProductTypesOperandsQuery,
_SearchProductTypesOperandsQueryVariables
>({
query: _SearchProductTypesOperandsDocument,
variables: {
productTypesSlugs: producttype,
first: producttype.length,
},
}),
);
}
if (Object.keys(attribute).length > 0) {
queriesToRun.push(
client.query<
_SearchAttributeOperandsQuery,
_SearchAttributeOperandsQueryVariables
>({
query: _SearchAttributeOperandsDocument,
variables: {
attributesSlugs: Object.keys(attribute),
choicesIds: Object.values<string[]>(attribute).flat(),
first: Object.keys(attribute).length,
},
}),
);
}
const data = await Promise.all(queriesToRun);
const initialState = createInitialStateFromData(data, channel);
setData(
new InitialStateResponse(
initialState.category,
initialState.attribute,
initialState.channel,
initialState.collection,
initialState.producttype,
),
);
setLoading(false);
};
return {
data,
loading,
fetchQueries,
};
};

View file

@ -17,10 +17,7 @@ export const ConditionalFilters: FC = () => {
{valueProvider.loading ? ( {valueProvider.loading ? (
<Text>Loading...</Text> <Text>Loading...</Text>
) : ( ) : (
<FiltersArea <FiltersArea onConfirm={handleConfirm} />
filterValue={valueProvider.value}
onConfirm={handleConfirm}
/>
)} )}
</Box> </Box>
); );

View file

@ -6,11 +6,10 @@ import { FilterContainer } from "./FilterElement";
import { useFilterContainer } from "./useFilterContainer"; import { useFilterContainer } from "./useFilterContainer";
interface FiltersAreaProps { interface FiltersAreaProps {
filterValue: FilterContainer;
onConfirm: (value: FilterContainer) => void; onConfirm: (value: FilterContainer) => void;
} }
export const FiltersArea = ({ filterValue, onConfirm }: FiltersAreaProps) => { export const FiltersArea = ({ onConfirm }: FiltersAreaProps) => {
const { apiProvider, leftOperandsProvider } = useConditionalFilterContext(); const { apiProvider, leftOperandsProvider } = useConditionalFilterContext();
const { const {
@ -22,7 +21,7 @@ export const FiltersArea = ({ filterValue, onConfirm }: FiltersAreaProps) => {
updateCondition, updateCondition,
updateRightOptions, updateRightOptions,
updateLeftOptions, updateLeftOptions,
} = useFilterContainer(filterValue, apiProvider, leftOperandsProvider); } = useFilterContainer(apiProvider, leftOperandsProvider);
const handleStateChange = async (event: FilterEvent["detail"]) => { const handleStateChange = async (event: FilterEvent["detail"]) => {
if (!event) return; if (!event) return;

View file

@ -1,7 +1,8 @@
import { stringify } from "qs"; import { stringify } from "qs";
import { useEffect } from "react";
import useRouter from "use-react-router"; import useRouter from "use-react-router";
import { FilterAPIProvider } from "../API/FilterAPIProvider"; import { InitialAPIState } from "../API";
import { FilterContainer } from "../FilterElement"; import { FilterContainer } from "../FilterElement";
import { FilterValueProvider } from "../FilterValueProvider"; import { FilterValueProvider } from "../FilterValueProvider";
import { useTokenArray } from "./TokenArray"; import { useTokenArray } from "./TokenArray";
@ -23,16 +24,21 @@ const prepareStructure = (filterValue: FilterContainer): Structure =>
}); });
export const useUrlValueProvider = ( export const useUrlValueProvider = (
apiProvider: FilterAPIProvider, initialState: InitialAPIState,
): FilterValueProvider => { ): FilterValueProvider => {
const router = useRouter(); const router = useRouter();
const params = new URLSearchParams(router.location.search); const params = new URLSearchParams(router.location.search);
const { data, loading, fetchQueries } = initialState;
params.delete("asc"); params.delete("asc");
params.delete("sort"); params.delete("sort");
const tokenizedUrl = useTokenArray(params.toString()); const tokenizedUrl = useTokenArray(params.toString());
const fetchingParams = tokenizedUrl.getFetchingParams(); const fetchingParams = tokenizedUrl.getFetchingParams();
const { data, loading } = apiProvider.useInitialState(fetchingParams); useEffect(() => {
fetchQueries(fetchingParams);
}, []);
const value = loading ? [] : tokenizedUrl.asFilterValuesFromResponse(data); const value = loading ? [] : tokenizedUrl.asFilterValuesFromResponse(data);
const persist = (filterValue: FilterContainer) => { const persist = (filterValue: FilterContainer) => {

View file

@ -3,9 +3,11 @@ import { createContext } from "react";
import { FilterAPIProvider } from "../API/FilterAPIProvider"; import { FilterAPIProvider } from "../API/FilterAPIProvider";
import { FilterValueProvider } from "../FilterValueProvider"; import { FilterValueProvider } from "../FilterValueProvider";
import { LeftOperandsProvider } from "../LeftOperandsProvider"; import { LeftOperandsProvider } from "../LeftOperandsProvider";
import { useContainerState } from "../useContainerState";
export const ConditionalFilterContext = createContext<{ export const ConditionalFilterContext = createContext<{
apiProvider: FilterAPIProvider; apiProvider: FilterAPIProvider;
valueProvider: FilterValueProvider; valueProvider: FilterValueProvider;
leftOperandsProvider: LeftOperandsProvider; leftOperandsProvider: LeftOperandsProvider;
containerState: ReturnType<typeof useContainerState>;
} | null>(null); } | null>(null);

View file

@ -1,14 +1,18 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { useProductInitialAPIState } from "../API/initialState/useInitialAPIState";
import { useProductFilterAPIProvider } from "../API/ProductFilterAPIProvider"; import { useProductFilterAPIProvider } from "../API/ProductFilterAPIProvider";
import { useContainerState } from "../useContainerState";
import { useFilterLeftOperandsProvider } from "../useFilterLeftOperands"; import { useFilterLeftOperandsProvider } from "../useFilterLeftOperands";
import { useUrlValueProvider } from "../ValueProvider/useUrlValueProvider"; import { useUrlValueProvider } from "../ValueProvider/useUrlValueProvider";
import { ConditionalFilterContext } from "./context"; import { ConditionalFilterContext } from "./context";
export const ConditionalProductFilterProvider: FC = ({ children }) => { export const ConditionalProductFilterProvider: FC = ({ children }) => {
const apiProvider = useProductFilterAPIProvider(); const apiProvider = useProductFilterAPIProvider();
const valueProvider = useUrlValueProvider(apiProvider); const initialState = useProductInitialAPIState();
const valueProvider = useUrlValueProvider(initialState);
const leftOperandsProvider = useFilterLeftOperandsProvider(); const leftOperandsProvider = useFilterLeftOperandsProvider();
const containerState = useContainerState(valueProvider.value);
return ( return (
<ConditionalFilterContext.Provider <ConditionalFilterContext.Provider
@ -16,6 +20,7 @@ export const ConditionalProductFilterProvider: FC = ({ children }) => {
apiProvider, apiProvider,
valueProvider, valueProvider,
leftOperandsProvider, leftOperandsProvider,
containerState,
}} }}
> >
{children} {children}

View file

@ -1,4 +1,4 @@
import { useState } from "react"; import { useEffect, useState } from "react";
import { FilterContainer, FilterElement } from "./FilterElement"; import { FilterContainer, FilterElement } from "./FilterElement";
@ -8,6 +8,12 @@ type Element = FilterContainer[number];
export const useContainerState = (initialValue: FilterContainer) => { export const useContainerState = (initialValue: FilterContainer) => {
const [value, setValue] = useState(initialValue); const [value, setValue] = useState(initialValue);
useEffect(() => {
if (value.length === 0 && initialValue.length > 0) {
setValue(initialValue);
}
}, [initialValue]);
const isFilterElement = ( const isFilterElement = (
elIndex: number, elIndex: number,
index: number, index: number,

View file

@ -1,18 +1,17 @@
import useDebounce from "@dashboard/hooks/useDebounce"; import useDebounce from "@dashboard/hooks/useDebounce";
import { FilterAPIProvider } from "./API/FilterAPIProvider"; import { FilterAPIProvider } from "./API/FilterAPIProvider";
import { FilterContainer } from "./FilterElement"; import { useConditionalFilterContext } from "./context";
import { ConditionValue, ItemOption } from "./FilterElement/ConditionValue"; import { ConditionValue, ItemOption } from "./FilterElement/ConditionValue";
import { LeftOperandsProvider } from "./LeftOperandsProvider"; import { LeftOperandsProvider } from "./LeftOperandsProvider";
import { useContainerState } from "./useContainerState";
export const useFilterContainer = ( export const useFilterContainer = (
initialValue: FilterContainer,
apiProvider: FilterAPIProvider, apiProvider: FilterAPIProvider,
leftOperandsProvider: LeftOperandsProvider, leftOperandsProvider: LeftOperandsProvider,
) => { ) => {
const { value, updateAt, removeAt, createEmpty } = const {
useContainerState(initialValue); containerState: { value, updateAt, removeAt, createEmpty },
} = useConditionalFilterContext();
const addEmpty = () => { const addEmpty = () => {
createEmpty(); createEmpty();