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 { ItemOption } from "../FilterElement/ConditionValue";
import { LeftOperand } from "../LeftOperandsProvider";
import { FetchingParams } from "../ValueProvider/TokenArray/fetchingParams";
import { InitialStateResponse } from "./InitialStateResponse";
export interface FilterAPIProvider {
fetchRightOptions: (
@ -11,8 +9,4 @@ export interface FilterAPIProvider {
inputValue: string,
) => Promise<ItemOption[]>;
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];
}
public static empty() {
return new InitialStateResponse([], {}, [], [], []);
}
public filterByUrlToken(token: UrlToken) {
if (token.isAttribute()) {
return this.attribute[token.name].choices.filter(({ value }) =>

View file

@ -1,7 +1,6 @@
import { ApolloClient, useApolloClient } from "@apollo/client";
import { FilterContainer, FilterElement } from "../FilterElement";
import { FetchingParams } from "../ValueProvider/TokenArray/fetchingParams";
import { FilterAPIProvider } from "./FilterAPIProvider";
import {
AttributeChoicesHandler,
@ -12,11 +11,6 @@ import {
Handler,
ProductTypeHandler,
} from "./Handler";
import {
createInitialStateFromData,
useDataFromAPI,
} from "./initialState/helpers";
import { InitialStateResponse } from "./InitialStateResponse";
const getFilterElement = (
value: FilterContainer,
@ -85,31 +79,8 @@ export const useProductFilterAPIProvider = (): FilterAPIProvider => {
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 {
fetchRightOptions,
fetchLeftOptions,
useInitialState,
};
};

View file

@ -1,24 +1,12 @@
import { ApolloQueryResult, useApolloClient } from "@apollo/client";
import { ApolloQueryResult } from "@apollo/client";
import {
_GetChannelOperandsDocument,
_GetChannelOperandsQuery,
_GetChannelOperandsQueryVariables,
_SearchAttributeOperandsDocument,
_SearchAttributeOperandsQuery,
_SearchAttributeOperandsQueryVariables,
_SearchCategoriesOperandsDocument,
_SearchCategoriesOperandsQuery,
_SearchCategoriesOperandsQueryVariables,
_SearchCollectionsOperandsDocument,
_SearchCollectionsOperandsQuery,
_SearchCollectionsOperandsQueryVariables,
_SearchProductTypesOperandsDocument,
_SearchProductTypesOperandsQuery,
_SearchProductTypesOperandsQueryVariables,
} from "@dashboard/graphql";
import { useEffect, useState } from "react";
import { FetchingParams } from "../../ValueProvider/TokenArray/fetchingParams";
import { createOptionsFromAPI } from "../Handler";
import { InitialState } from "../InitialStateResponse";
import { InitialAPIResponse } from "./types";
@ -118,104 +106,3 @@ export const createInitialStateFromData = (
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 ? (
<Text>Loading...</Text>
) : (
<FiltersArea
filterValue={valueProvider.value}
onConfirm={handleConfirm}
/>
<FiltersArea onConfirm={handleConfirm} />
)}
</Box>
);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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