From 1dd0488c62d935324c17fcb10479b2331b2e49aa Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Wed, 13 Nov 2019 17:46:08 +0100 Subject: [PATCH] Use mutation hooks --- package.json | 2 +- src/categories/mutations.ts | 10 +- src/categories/views/CategoryCreate.tsx | 97 ++- src/categories/views/CategoryDetails.tsx | 598 ++++++++---------- .../views/CategoryList/CategoryList.tsx | 224 ++++--- src/hooks/makeMutation.ts | 65 ++ 6 files changed, 506 insertions(+), 490 deletions(-) create mode 100644 src/hooks/makeMutation.ts diff --git a/package.json b/package.json index e0895b046..31d82bf87 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "moment-timezone": "^0.5.26", "qs": "^6.9.0", "react": "^16.9.0", - "react-apollo": "^3.0.0", + "react-apollo": "^3.1.3", "react-dom": "^16.9.0", "react-dropzone": "^8.2.0", "react-error-boundary": "^1.2.5", diff --git a/src/categories/mutations.ts b/src/categories/mutations.ts index 715aa4bbf..7995b3c6f 100644 --- a/src/categories/mutations.ts +++ b/src/categories/mutations.ts @@ -1,6 +1,6 @@ import gql from "graphql-tag"; -import { TypedMutation } from "../mutations"; +import makeMutation from "@saleor/hooks/makeMutation"; import { categoryDetailsFragment } from "./queries"; import { CategoryBulkDelete, @@ -29,7 +29,7 @@ export const categoryDeleteMutation = gql` } } `; -export const TypedCategoryDeleteMutation = TypedMutation< +export const useCategoryDeleteMutation = makeMutation< CategoryDelete, CategoryDeleteVariables >(categoryDeleteMutation); @@ -48,7 +48,7 @@ export const categoryCreateMutation = gql` } } `; -export const TypedCategoryCreateMutation = TypedMutation< +export const useCategoryCreateMutation = makeMutation< CategoryCreate, CategoryCreateVariables >(categoryCreateMutation); @@ -67,7 +67,7 @@ export const categoryUpdateMutation = gql` } } `; -export const TypedCategoryUpdateMutation = TypedMutation< +export const useCategoryUpdateMutation = makeMutation< CategoryUpdate, CategoryUpdateVariables >(categoryUpdateMutation); @@ -82,7 +82,7 @@ export const categoryBulkDeleteMutation = gql` } } `; -export const TypedCategoryBulkDeleteMutation = TypedMutation< +export const useCategoryBulkDeleteMutation = makeMutation< CategoryBulkDelete, CategoryBulkDeleteVariables >(categoryBulkDeleteMutation); diff --git a/src/categories/views/CategoryCreate.tsx b/src/categories/views/CategoryCreate.tsx index 90d1df35d..592552c00 100644 --- a/src/categories/views/CategoryCreate.tsx +++ b/src/categories/views/CategoryCreate.tsx @@ -6,7 +6,7 @@ import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; import { getMutationState, maybe } from "../../misc"; import CategoryCreatePage from "../components/CategoryCreatePage"; -import { TypedCategoryCreateMutation } from "../mutations"; +import { useCategoryCreateMutation } from "../mutations"; import { CategoryCreate } from "../types/CategoryCreate"; import { categoryListUrl, categoryUrl } from "../urls"; @@ -31,55 +31,54 @@ export const CategoryCreateView: React.FC = ({ navigate(categoryUrl(data.categoryCreate.category.id)); } }; + + const [createCategory, createCategoryResult] = useCategoryCreateMutation({ + onCompleted: handleSuccess + }); + + const errors = maybe( + () => createCategoryResult.data.categoryCreate.errors, + [] + ); + + const formTransitionState = getMutationState( + createCategoryResult.called, + createCategoryResult.loading, + errors + ); + return ( - - {(createCategory, createCategoryResult) => { - const errors = maybe( - () => createCategoryResult.data.categoryCreate.errors, - [] - ); - - const formTransitionState = getMutationState( - createCategoryResult.called, - createCategoryResult.loading, - errors - ); - - return ( - <> - - - navigate(parentId ? categoryUrl(parentId) : categoryListUrl()) - } - onSubmit={formData => - createCategory({ - variables: { - input: { - descriptionJson: JSON.stringify(formData.description), - name: formData.name, - seo: { - description: formData.seoDescription, - title: formData.seoTitle - } - }, - parent: parentId || null - } - }) - } - /> - - ); - }} - + <> + + + navigate(parentId ? categoryUrl(parentId) : categoryListUrl()) + } + onSubmit={formData => + createCategory({ + variables: { + input: { + descriptionJson: JSON.stringify(formData.description), + name: formData.name, + seo: { + description: formData.seoDescription, + title: formData.seoTitle + } + }, + parent: parentId || null + } + }) + } + /> + ); }; export default CategoryCreateView; diff --git a/src/categories/views/CategoryDetails.tsx b/src/categories/views/CategoryDetails.tsx index 8a4cf6002..51046d603 100644 --- a/src/categories/views/CategoryDetails.tsx +++ b/src/categories/views/CategoryDetails.tsx @@ -24,9 +24,9 @@ import { CategoryUpdatePage } from "../components/CategoryUpdatePage/CategoryUpdatePage"; import { - TypedCategoryBulkDeleteMutation, - TypedCategoryDeleteMutation, - TypedCategoryUpdateMutation + useCategoryBulkDeleteMutation, + useCategoryDeleteMutation, + useCategoryUpdateMutation } from "../mutations"; import { TypedCategoryDetailsQuery } from "../queries"; import { CategoryBulkDelete } from "../types/CategoryBulkDelete"; @@ -73,6 +73,11 @@ export const CategoryDetails: React.FC = ({ navigate(categoryListUrl()); } }; + + const [deleteCategory, deleteResult] = useCategoryDeleteMutation({ + onCompleted: handleCategoryDelete + }); + const handleCategoryUpdate = (data: CategoryUpdate) => { if (data.categoryUpdate.errors.length > 0) { const backgroundImageError = data.categoryUpdate.errors.find( @@ -86,6 +91,27 @@ export const CategoryDetails: React.FC = ({ } }; + const [updateCategory, updateResult] = useCategoryUpdateMutation({ + onCompleted: handleCategoryUpdate + }); + + const handleBulkCategoryDelete = (data: CategoryBulkDelete) => { + if (data.categoryBulkDelete.errors.length === 0) { + closeModal(); + notify({ + text: intl.formatMessage(commonMessages.savedChanges) + }); + reset(); + } + }; + + const [ + categoryBulkDelete, + categoryBulkDeleteOpts + ] = useCategoryBulkDeleteMutation({ + onCompleted: handleBulkCategoryDelete + }); + const changeTab = (tabName: CategoryPageTab) => { reset(); navigate( @@ -114,329 +140,257 @@ export const CategoryDetails: React.FC = ({ }) ); + const paginationState = createPaginationState(PAGINATE_BY, params); + const formTransitionState = getMutationState( + updateResult.called, + updateResult.loading, + maybe(() => updateResult.data.categoryUpdate.errors) + ); + const removeDialogTransitionState = getMutationState( + deleteResult.called, + deleteResult.loading, + maybe(() => deleteResult.data.categoryDelete.errors) + ); + return ( - - {(deleteCategory, deleteResult) => ( - - {(updateCategory, updateResult) => { - const paginationState = createPaginationState(PAGINATE_BY, params); - const formTransitionState = getMutationState( - updateResult.called, - updateResult.loading, - maybe(() => updateResult.data.categoryUpdate.errors) - ); - const removeDialogTransitionState = getMutationState( - deleteResult.called, - deleteResult.loading, - maybe(() => deleteResult.data.categoryDelete.errors) - ); - return ( - - {({ data, loading, refetch }) => { - const handleBulkCategoryDelete = ( - data: CategoryBulkDelete - ) => { - if (data.categoryBulkDelete.errors.length === 0) { - closeModal(); - notify({ - text: intl.formatMessage(commonMessages.savedChanges) - }); - refetch(); - reset(); - } - }; + + {({ data, loading, refetch }) => { + const handleBulkProductDelete = (data: productBulkDelete) => { + if (data.productBulkDelete.errors.length === 0) { + closeModal(); + notify({ + text: intl.formatMessage(commonMessages.savedChanges) + }); + refetch(); + reset(); + } + }; - const handleBulkProductDelete = (data: productBulkDelete) => { - if (data.productBulkDelete.errors.length === 0) { - closeModal(); - notify({ - text: intl.formatMessage(commonMessages.savedChanges) - }); - refetch(); - reset(); - } - }; + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + params.activeTab === CategoryPageTab.categories + ? maybe(() => data.category.children.pageInfo) + : maybe(() => data.category.products.pageInfo), + paginationState, + params + ); - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - params.activeTab === CategoryPageTab.categories - ? maybe(() => data.category.children.pageInfo) - : maybe(() => data.category.products.pageInfo), - paginationState, - params - ); + return ( + <> + data.category.name)} /> + + {(productBulkDelete, productBulkDeleteOpts) => { + const categoryBulkDeleteMutationState = getMutationState( + categoryBulkDeleteOpts.called, + categoryBulkDeleteOpts.loading, + maybe( + () => categoryBulkDeleteOpts.data.categoryBulkDelete.errors + ) + ); + const productBulkDeleteMutationState = getMutationState( + productBulkDeleteOpts.called, + productBulkDeleteOpts.loading, + maybe( + () => productBulkDeleteOpts.data.productBulkDelete.errors + ) + ); - return ( - <> - data.category.name)} /> - - {(categoryBulkDelete, categoryBulkDeleteOpts) => ( - - {(productBulkDelete, productBulkDeleteOpts) => { - const categoryBulkDeleteMutationState = getMutationState( - categoryBulkDeleteOpts.called, - categoryBulkDeleteOpts.loading, - maybe( - () => - categoryBulkDeleteOpts.data - .categoryBulkDelete.errors - ) - ); - const productBulkDeleteMutationState = getMutationState( - productBulkDeleteOpts.called, - productBulkDeleteOpts.loading, - maybe( - () => - productBulkDeleteOpts.data.productBulkDelete - .errors - ) - ); - - return ( - <> - data.category)} - disabled={loading} - errors={maybe( - () => - updateResult.data.categoryUpdate.errors - )} - onAddCategory={() => - navigate(categoryAddUrl(id)) - } - onAddProduct={() => navigate(productAddUrl)} - onBack={() => - navigate( - maybe( - () => - categoryUrl( - data.category.parent.id - ), - categoryListUrl() - ) - ) - } - onCategoryClick={id => () => - navigate(categoryUrl(id))} - onDelete={() => openModal("delete")} - onImageDelete={() => - updateCategory({ - variables: { - id, - input: { - backgroundImage: null - } - } - }) - } - onImageUpload={file => - updateCategory({ - variables: { - id, - input: { - backgroundImage: file - } - } - }) - } - onNextPage={loadNextPage} - onPreviousPage={loadPreviousPage} - pageInfo={pageInfo} - onProductClick={id => () => - navigate(productUrl(id))} - onSubmit={formData => - updateCategory({ - variables: { - id, - input: { - backgroundImageAlt: - formData.backgroundImageAlt, - descriptionJson: JSON.stringify( - formData.description - ), - name: formData.name, - seo: { - description: - formData.seoDescription, - title: formData.seoTitle - } - } - } - }) - } - products={maybe(() => - data.category.products.edges.map( - edge => edge.node - ) - )} - saveButtonBarState={formTransitionState} - subcategories={maybe(() => - data.category.children.edges.map( - edge => edge.node - ) - )} - subcategoryListToolbar={ - - openModal( - "delete-categories", - listElements - ) - } - > - - - } - productListToolbar={ - - openModal( - "delete-products", - listElements - ) - } - > - - - } - isChecked={isSelected} - selected={listElements.length} - toggle={toggle} - toggleAll={toggleAll} - /> - - deleteCategory({ variables: { id } }) - } - open={params.action === "delete"} - title={intl.formatMessage({ - defaultMessage: "Delete category", - description: "dialog title" - })} - variant="delete" - > - - - {maybe( - () => data.category.name, - "..." - )} - - ) - }} - /> - - - - - - params.ids.length > 0) - } - confirmButtonState={ - categoryBulkDeleteMutationState - } - onClose={closeModal} - onConfirm={() => - categoryBulkDelete({ - variables: { ids: params.ids } - }) - } - title={intl.formatMessage({ - defaultMessage: "Delete categories", - description: "dialog title" - })} - variant="delete" - > - - params.ids.length - ), - displayQuantity: ( - - {maybe(() => params.ids.length)} - - ) - }} - /> - - - - - - - productBulkDelete({ - variables: { ids: params.ids } - }) - } - title={intl.formatMessage({ - defaultMessage: "Delete products", - description: "dialog title" - })} - variant="delete" - > - {" "} - - params.ids.length - ), - displayQuantity: ( - - {maybe(() => params.ids.length)} - - ) - }} - /> - - - - ); - }} - - )} - - - ); - }} - - ); - }} - - )} - + return ( + <> + data.category)} + disabled={loading} + errors={maybe( + () => updateResult.data.categoryUpdate.errors + )} + onAddCategory={() => navigate(categoryAddUrl(id))} + onAddProduct={() => navigate(productAddUrl)} + onBack={() => + navigate( + maybe( + () => categoryUrl(data.category.parent.id), + categoryListUrl() + ) + ) + } + onCategoryClick={id => () => navigate(categoryUrl(id))} + onDelete={() => openModal("delete")} + onImageDelete={() => + updateCategory({ + variables: { + id, + input: { + backgroundImage: null + } + } + }) + } + onImageUpload={file => + updateCategory({ + variables: { + id, + input: { + backgroundImage: file + } + } + }) + } + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + pageInfo={pageInfo} + onProductClick={id => () => navigate(productUrl(id))} + onSubmit={formData => + updateCategory({ + variables: { + id, + input: { + backgroundImageAlt: formData.backgroundImageAlt, + descriptionJson: JSON.stringify( + formData.description + ), + name: formData.name, + seo: { + description: formData.seoDescription, + title: formData.seoTitle + } + } + } + }) + } + products={maybe(() => + data.category.products.edges.map(edge => edge.node) + )} + saveButtonBarState={formTransitionState} + subcategories={maybe(() => + data.category.children.edges.map(edge => edge.node) + )} + subcategoryListToolbar={ + + openModal("delete-categories", listElements) + } + > + + + } + productListToolbar={ + + openModal("delete-products", listElements) + } + > + + + } + isChecked={isSelected} + selected={listElements.length} + toggle={toggle} + toggleAll={toggleAll} + /> + deleteCategory({ variables: { id } })} + open={params.action === "delete"} + title={intl.formatMessage({ + defaultMessage: "Delete category", + description: "dialog title" + })} + variant="delete" + > + + + {maybe(() => data.category.name, "...")} + + ) + }} + /> + + + + + + params.ids.length > 0) + } + confirmButtonState={categoryBulkDeleteMutationState} + onClose={closeModal} + onConfirm={() => + categoryBulkDelete({ + variables: { ids: params.ids } + }).then(() => refetch()) + } + title={intl.formatMessage({ + defaultMessage: "Delete categories", + description: "dialog title" + })} + variant="delete" + > + + params.ids.length), + displayQuantity: ( + {maybe(() => params.ids.length)} + ) + }} + /> + + + + + + + productBulkDelete({ + variables: { ids: params.ids } + }).then(() => refetch()) + } + title={intl.formatMessage({ + defaultMessage: "Delete products", + description: "dialog title" + })} + variant="delete" + > + + params.ids.length), + displayQuantity: ( + {maybe(() => params.ids.length)} + ) + }} + /> + + + + ); + }} + + + ); + }} + ); }; export default CategoryDetails; diff --git a/src/categories/views/CategoryList/CategoryList.tsx b/src/categories/views/CategoryList/CategoryList.tsx index d3ec14a44..e0b4ee657 100644 --- a/src/categories/views/CategoryList/CategoryList.tsx +++ b/src/categories/views/CategoryList/CategoryList.tsx @@ -18,7 +18,7 @@ import usePaginator, { import { getMutationState, maybe } from "@saleor/misc"; import { ListViews } from "@saleor/types"; import { CategoryListPage } from "../../components/CategoryListPage/CategoryListPage"; -import { TypedCategoryBulkDeleteMutation } from "../../mutations"; +import { useCategoryBulkDeleteMutation } from "../../mutations"; import { TypedRootCategoriesQuery } from "../../queries"; import { CategoryBulkDelete } from "../../types/CategoryBulkDelete"; import { @@ -138,120 +138,118 @@ export const CategoryList: React.FC = ({ params }) => { reset(); } }; - return ( - - {(categoryBulkDelete, categoryBulkDeleteOpts) => { - const bulkDeleteState = getMutationState( - categoryBulkDeleteOpts.called, - categoryBulkDeleteOpts.loading, - maybe( - () => categoryBulkDeleteOpts.data.categoryBulkDelete.errors - ) - ); - return ( - <> - data.categories.edges.map(edge => edge.node), - [] - )} - currentTab={currentTab} - initialSearch={params.query || ""} - onSearchChange={query => changeFilterField({ query })} - onAll={() => navigate(categoryListUrl())} - onTabChange={handleTabChange} - onTabDelete={() => openModal("delete-search")} - onTabSave={() => openModal("save-search")} - tabs={tabs.map(tab => tab.name)} - settings={settings} - onAdd={() => navigate(categoryAddUrl())} - onRowClick={id => () => navigate(categoryUrl(id))} - disabled={loading} - onNextPage={loadNextPage} - onPreviousPage={loadPreviousPage} - onUpdateListSettings={updateListSettings} - pageInfo={pageInfo} - isChecked={isSelected} - selected={listElements.length} - toggle={toggle} - toggleAll={toggleAll} - toolbar={ - - navigate( - categoryListUrl({ - ...params, - action: "delete", - ids: listElements - }) - ) - } - > - - - } - /> - - navigate( - categoryListUrl({ - ...params, - action: undefined, - ids: undefined - }) - ) - } - onConfirm={() => - categoryBulkDelete({ - variables: { - ids: params.ids - } + const [ + categoryBulkDelete, + categoryBulkDeleteOpts + ] = useCategoryBulkDeleteMutation({ + onCompleted: handleCategoryBulkDelete + }); + + const bulkDeleteState = getMutationState( + categoryBulkDeleteOpts.called, + categoryBulkDeleteOpts.loading, + maybe(() => categoryBulkDeleteOpts.data.categoryBulkDelete.errors) + ); + + return ( + <> + data.categories.edges.map(edge => edge.node), + [] + )} + currentTab={currentTab} + initialSearch={params.query || ""} + onSearchChange={query => changeFilterField({ query })} + onAll={() => navigate(categoryListUrl())} + onTabChange={handleTabChange} + onTabDelete={() => openModal("delete-search")} + onTabSave={() => openModal("save-search")} + tabs={tabs.map(tab => tab.name)} + settings={settings} + onAdd={() => navigate(categoryAddUrl())} + onRowClick={id => () => navigate(categoryUrl(id))} + disabled={loading} + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onUpdateListSettings={updateListSettings} + pageInfo={pageInfo} + isChecked={isSelected} + selected={listElements.length} + toggle={toggle} + toggleAll={toggleAll} + toolbar={ + + navigate( + categoryListUrl({ + ...params, + action: "delete", + ids: listElements }) - } - open={params.action === "delete"} - title={intl.formatMessage({ - defaultMessage: "Delete categories", - description: "dialog title" - })} - variant="delete" - > - - params.ids.length), - displayQuantity: ( - {maybe(() => params.ids.length)} - ) - }} - /> - - - - - - - tabs[currentTab - 1].name, "...")} - /> - - ); - }} - + ) + } + > + + + } + /> + + navigate( + categoryListUrl({ + ...params, + action: undefined, + ids: undefined + }) + ) + } + onConfirm={() => + categoryBulkDelete({ + variables: { + ids: params.ids + } + }) + } + open={params.action === "delete"} + title={intl.formatMessage({ + defaultMessage: "Delete categories", + description: "dialog title" + })} + variant="delete" + > + + params.ids.length), + displayQuantity: ( + {maybe(() => params.ids.length)} + ) + }} + /> + + + + + + + tabs[currentTab - 1].name, "...")} + /> + ); }} diff --git a/src/hooks/makeMutation.ts b/src/hooks/makeMutation.ts new file mode 100644 index 000000000..b6cfd4a91 --- /dev/null +++ b/src/hooks/makeMutation.ts @@ -0,0 +1,65 @@ +import { ApolloError } from "apollo-client"; +import { DocumentNode } from "graphql"; +import { + MutationFunction, + MutationResult, + useMutation as useBaseMutation +} from "react-apollo"; +import { useIntl } from "react-intl"; + +import { commonMessages } from "@saleor/intl"; +import { maybe } from "@saleor/misc"; +import useNotifier from "./useNotifier"; + +type UseMutation = [ + MutationFunction, + MutationResult +]; +type UseMutationCbs = Partial<{ + onCompleted: (data: TData) => void; + onError: (error: ApolloError) => void; +}>; +type UseMutationHook = ( + cbs: UseMutationCbs +) => UseMutation; + +function makeMutation( + mutation: DocumentNode +): UseMutationHook { + function useMutation({ + onCompleted, + onError + }: UseMutationCbs): UseMutation { + const notify = useNotifier(); + const intl = useIntl(); + const [mutateFn, result] = useBaseMutation(mutation, { + onCompleted, + onError: (err: ApolloError) => { + if ( + maybe( + () => + err.graphQLErrors[0].extensions.exception.code === + "ReadOnlyException" + ) + ) { + notify({ + text: intl.formatMessage(commonMessages.readOnly) + }); + } else { + notify({ + text: intl.formatMessage(commonMessages.somethingWentWrong) + }); + } + if (onError) { + onError(err); + } + } + }); + + return [mutateFn, result]; + } + + return useMutation; +} + +export default makeMutation;