Use mutation hooks

This commit is contained in:
dominik-zeglen 2019-11-13 17:46:08 +01:00
parent 8170f9e14e
commit 1dd0488c62
6 changed files with 506 additions and 490 deletions

View file

@ -47,7 +47,7 @@
"moment-timezone": "^0.5.26", "moment-timezone": "^0.5.26",
"qs": "^6.9.0", "qs": "^6.9.0",
"react": "^16.9.0", "react": "^16.9.0",
"react-apollo": "^3.0.0", "react-apollo": "^3.1.3",
"react-dom": "^16.9.0", "react-dom": "^16.9.0",
"react-dropzone": "^8.2.0", "react-dropzone": "^8.2.0",
"react-error-boundary": "^1.2.5", "react-error-boundary": "^1.2.5",

View file

@ -1,6 +1,6 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import { TypedMutation } from "../mutations"; import makeMutation from "@saleor/hooks/makeMutation";
import { categoryDetailsFragment } from "./queries"; import { categoryDetailsFragment } from "./queries";
import { import {
CategoryBulkDelete, CategoryBulkDelete,
@ -29,7 +29,7 @@ export const categoryDeleteMutation = gql`
} }
} }
`; `;
export const TypedCategoryDeleteMutation = TypedMutation< export const useCategoryDeleteMutation = makeMutation<
CategoryDelete, CategoryDelete,
CategoryDeleteVariables CategoryDeleteVariables
>(categoryDeleteMutation); >(categoryDeleteMutation);
@ -48,7 +48,7 @@ export const categoryCreateMutation = gql`
} }
} }
`; `;
export const TypedCategoryCreateMutation = TypedMutation< export const useCategoryCreateMutation = makeMutation<
CategoryCreate, CategoryCreate,
CategoryCreateVariables CategoryCreateVariables
>(categoryCreateMutation); >(categoryCreateMutation);
@ -67,7 +67,7 @@ export const categoryUpdateMutation = gql`
} }
} }
`; `;
export const TypedCategoryUpdateMutation = TypedMutation< export const useCategoryUpdateMutation = makeMutation<
CategoryUpdate, CategoryUpdate,
CategoryUpdateVariables CategoryUpdateVariables
>(categoryUpdateMutation); >(categoryUpdateMutation);
@ -82,7 +82,7 @@ export const categoryBulkDeleteMutation = gql`
} }
} }
`; `;
export const TypedCategoryBulkDeleteMutation = TypedMutation< export const useCategoryBulkDeleteMutation = makeMutation<
CategoryBulkDelete, CategoryBulkDelete,
CategoryBulkDeleteVariables CategoryBulkDeleteVariables
>(categoryBulkDeleteMutation); >(categoryBulkDeleteMutation);

View file

@ -6,7 +6,7 @@ import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import { getMutationState, maybe } from "../../misc"; import { getMutationState, maybe } from "../../misc";
import CategoryCreatePage from "../components/CategoryCreatePage"; import CategoryCreatePage from "../components/CategoryCreatePage";
import { TypedCategoryCreateMutation } from "../mutations"; import { useCategoryCreateMutation } from "../mutations";
import { CategoryCreate } from "../types/CategoryCreate"; import { CategoryCreate } from "../types/CategoryCreate";
import { categoryListUrl, categoryUrl } from "../urls"; import { categoryListUrl, categoryUrl } from "../urls";
@ -31,9 +31,11 @@ export const CategoryCreateView: React.FC<CategoryCreateViewProps> = ({
navigate(categoryUrl(data.categoryCreate.category.id)); navigate(categoryUrl(data.categoryCreate.category.id));
} }
}; };
return (
<TypedCategoryCreateMutation onCompleted={handleSuccess}> const [createCategory, createCategoryResult] = useCategoryCreateMutation({
{(createCategory, createCategoryResult) => { onCompleted: handleSuccess
});
const errors = maybe( const errors = maybe(
() => createCategoryResult.data.categoryCreate.errors, () => createCategoryResult.data.categoryCreate.errors,
[] []
@ -78,8 +80,5 @@ export const CategoryCreateView: React.FC<CategoryCreateViewProps> = ({
/> />
</> </>
); );
}}
</TypedCategoryCreateMutation>
);
}; };
export default CategoryCreateView; export default CategoryCreateView;

View file

@ -24,9 +24,9 @@ import {
CategoryUpdatePage CategoryUpdatePage
} from "../components/CategoryUpdatePage/CategoryUpdatePage"; } from "../components/CategoryUpdatePage/CategoryUpdatePage";
import { import {
TypedCategoryBulkDeleteMutation, useCategoryBulkDeleteMutation,
TypedCategoryDeleteMutation, useCategoryDeleteMutation,
TypedCategoryUpdateMutation useCategoryUpdateMutation
} from "../mutations"; } from "../mutations";
import { TypedCategoryDetailsQuery } from "../queries"; import { TypedCategoryDetailsQuery } from "../queries";
import { CategoryBulkDelete } from "../types/CategoryBulkDelete"; import { CategoryBulkDelete } from "../types/CategoryBulkDelete";
@ -73,6 +73,11 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
navigate(categoryListUrl()); navigate(categoryListUrl());
} }
}; };
const [deleteCategory, deleteResult] = useCategoryDeleteMutation({
onCompleted: handleCategoryDelete
});
const handleCategoryUpdate = (data: CategoryUpdate) => { const handleCategoryUpdate = (data: CategoryUpdate) => {
if (data.categoryUpdate.errors.length > 0) { if (data.categoryUpdate.errors.length > 0) {
const backgroundImageError = data.categoryUpdate.errors.find( const backgroundImageError = data.categoryUpdate.errors.find(
@ -86,6 +91,27 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
} }
}; };
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) => { const changeTab = (tabName: CategoryPageTab) => {
reset(); reset();
navigate( navigate(
@ -114,11 +140,6 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
}) })
); );
return (
<TypedCategoryDeleteMutation onCompleted={handleCategoryDelete}>
{(deleteCategory, deleteResult) => (
<TypedCategoryUpdateMutation onCompleted={handleCategoryUpdate}>
{(updateCategory, updateResult) => {
const paginationState = createPaginationState(PAGINATE_BY, params); const paginationState = createPaginationState(PAGINATE_BY, params);
const formTransitionState = getMutationState( const formTransitionState = getMutationState(
updateResult.called, updateResult.called,
@ -130,6 +151,7 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
deleteResult.loading, deleteResult.loading,
maybe(() => deleteResult.data.categoryDelete.errors) maybe(() => deleteResult.data.categoryDelete.errors)
); );
return ( return (
<TypedCategoryDetailsQuery <TypedCategoryDetailsQuery
displayLoader displayLoader
@ -137,19 +159,6 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
require={["category"]} require={["category"]}
> >
{({ data, loading, refetch }) => { {({ data, loading, refetch }) => {
const handleBulkCategoryDelete = (
data: CategoryBulkDelete
) => {
if (data.categoryBulkDelete.errors.length === 0) {
closeModal();
notify({
text: intl.formatMessage(commonMessages.savedChanges)
});
refetch();
reset();
}
};
const handleBulkProductDelete = (data: productBulkDelete) => { const handleBulkProductDelete = (data: productBulkDelete) => {
if (data.productBulkDelete.errors.length === 0) { if (data.productBulkDelete.errors.length === 0) {
closeModal(); closeModal();
@ -172,10 +181,6 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
return ( return (
<> <>
<WindowTitle title={maybe(() => data.category.name)} /> <WindowTitle title={maybe(() => data.category.name)} />
<TypedCategoryBulkDeleteMutation
onCompleted={handleBulkCategoryDelete}
>
{(categoryBulkDelete, categoryBulkDeleteOpts) => (
<TypedProductBulkDeleteMutation <TypedProductBulkDeleteMutation
onCompleted={handleBulkProductDelete} onCompleted={handleBulkProductDelete}
> >
@ -184,18 +189,14 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
categoryBulkDeleteOpts.called, categoryBulkDeleteOpts.called,
categoryBulkDeleteOpts.loading, categoryBulkDeleteOpts.loading,
maybe( maybe(
() => () => categoryBulkDeleteOpts.data.categoryBulkDelete.errors
categoryBulkDeleteOpts.data
.categoryBulkDelete.errors
) )
); );
const productBulkDeleteMutationState = getMutationState( const productBulkDeleteMutationState = getMutationState(
productBulkDeleteOpts.called, productBulkDeleteOpts.called,
productBulkDeleteOpts.loading, productBulkDeleteOpts.loading,
maybe( maybe(
() => () => productBulkDeleteOpts.data.productBulkDelete.errors
productBulkDeleteOpts.data.productBulkDelete
.errors
) )
); );
@ -207,26 +208,19 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
category={maybe(() => data.category)} category={maybe(() => data.category)}
disabled={loading} disabled={loading}
errors={maybe( errors={maybe(
() => () => updateResult.data.categoryUpdate.errors
updateResult.data.categoryUpdate.errors
)} )}
onAddCategory={() => onAddCategory={() => navigate(categoryAddUrl(id))}
navigate(categoryAddUrl(id))
}
onAddProduct={() => navigate(productAddUrl)} onAddProduct={() => navigate(productAddUrl)}
onBack={() => onBack={() =>
navigate( navigate(
maybe( maybe(
() => () => categoryUrl(data.category.parent.id),
categoryUrl(
data.category.parent.id
),
categoryListUrl() categoryListUrl()
) )
) )
} }
onCategoryClick={id => () => onCategoryClick={id => () => navigate(categoryUrl(id))}
navigate(categoryUrl(id))}
onDelete={() => openModal("delete")} onDelete={() => openModal("delete")}
onImageDelete={() => onImageDelete={() =>
updateCategory({ updateCategory({
@ -251,22 +245,19 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
onNextPage={loadNextPage} onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage} onPreviousPage={loadPreviousPage}
pageInfo={pageInfo} pageInfo={pageInfo}
onProductClick={id => () => onProductClick={id => () => navigate(productUrl(id))}
navigate(productUrl(id))}
onSubmit={formData => onSubmit={formData =>
updateCategory({ updateCategory({
variables: { variables: {
id, id,
input: { input: {
backgroundImageAlt: backgroundImageAlt: formData.backgroundImageAlt,
formData.backgroundImageAlt,
descriptionJson: JSON.stringify( descriptionJson: JSON.stringify(
formData.description formData.description
), ),
name: formData.name, name: formData.name,
seo: { seo: {
description: description: formData.seoDescription,
formData.seoDescription,
title: formData.seoTitle title: formData.seoTitle
} }
} }
@ -274,24 +265,17 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
}) })
} }
products={maybe(() => products={maybe(() =>
data.category.products.edges.map( data.category.products.edges.map(edge => edge.node)
edge => edge.node
)
)} )}
saveButtonBarState={formTransitionState} saveButtonBarState={formTransitionState}
subcategories={maybe(() => subcategories={maybe(() =>
data.category.children.edges.map( data.category.children.edges.map(edge => edge.node)
edge => edge.node
)
)} )}
subcategoryListToolbar={ subcategoryListToolbar={
<IconButton <IconButton
color="primary" color="primary"
onClick={() => onClick={() =>
openModal( openModal("delete-categories", listElements)
"delete-categories",
listElements
)
} }
> >
<DeleteIcon /> <DeleteIcon />
@ -301,10 +285,7 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
<IconButton <IconButton
color="primary" color="primary"
onClick={() => onClick={() =>
openModal( openModal("delete-products", listElements)
"delete-products",
listElements
)
} }
> >
<DeleteIcon /> <DeleteIcon />
@ -316,13 +297,9 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
toggleAll={toggleAll} toggleAll={toggleAll}
/> />
<ActionDialog <ActionDialog
confirmButtonState={ confirmButtonState={removeDialogTransitionState}
removeDialogTransitionState
}
onClose={closeModal} onClose={closeModal}
onConfirm={() => onConfirm={() => deleteCategory({ variables: { id } })}
deleteCategory({ variables: { id } })
}
open={params.action === "delete"} open={params.action === "delete"}
title={intl.formatMessage({ title={intl.formatMessage({
defaultMessage: "Delete category", defaultMessage: "Delete category",
@ -336,10 +313,7 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
values={{ values={{
categoryName: ( categoryName: (
<strong> <strong>
{maybe( {maybe(() => data.category.name, "...")}
() => data.category.name,
"..."
)}
</strong> </strong>
) )
}} }}
@ -354,14 +328,12 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
params.action === "delete-categories" && params.action === "delete-categories" &&
maybe(() => params.ids.length > 0) maybe(() => params.ids.length > 0)
} }
confirmButtonState={ confirmButtonState={categoryBulkDeleteMutationState}
categoryBulkDeleteMutationState
}
onClose={closeModal} onClose={closeModal}
onConfirm={() => onConfirm={() =>
categoryBulkDelete({ categoryBulkDelete({
variables: { ids: params.ids } variables: { ids: params.ids }
}) }).then(() => refetch())
} }
title={intl.formatMessage({ title={intl.formatMessage({
defaultMessage: "Delete categories", defaultMessage: "Delete categories",
@ -371,15 +343,11 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
> >
<DialogContentText> <DialogContentText>
<FormattedMessage <FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this attribute} other{{displayQuantity} categories}}?" defaultMessage="Are you sure you want to delete {counter,plural,one{this category} other{{displayQuantity} categories}}?"
values={{ values={{
counter: maybe( counter: maybe(() => params.ids.length),
() => params.ids.length
),
displayQuantity: ( displayQuantity: (
<strong> <strong>{maybe(() => params.ids.length)}</strong>
{maybe(() => params.ids.length)}
</strong>
) )
}} }}
/> />
@ -390,14 +358,12 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
</ActionDialog> </ActionDialog>
<ActionDialog <ActionDialog
open={params.action === "delete-products"} open={params.action === "delete-products"}
confirmButtonState={ confirmButtonState={productBulkDeleteMutationState}
productBulkDeleteMutationState
}
onClose={closeModal} onClose={closeModal}
onConfirm={() => onConfirm={() =>
productBulkDelete({ productBulkDelete({
variables: { ids: params.ids } variables: { ids: params.ids }
}) }).then(() => refetch())
} }
title={intl.formatMessage({ title={intl.formatMessage({
defaultMessage: "Delete products", defaultMessage: "Delete products",
@ -405,18 +371,13 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
})} })}
variant="delete" variant="delete"
> >
{" "}
<DialogContentText> <DialogContentText>
<FormattedMessage <FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this attribute} other{{displayQuantity} products}}?" defaultMessage="Are you sure you want to delete {counter,plural,one{this product} other{{displayQuantity} products}}?"
values={{ values={{
counter: maybe( counter: maybe(() => params.ids.length),
() => params.ids.length
),
displayQuantity: ( displayQuantity: (
<strong> <strong>{maybe(() => params.ids.length)}</strong>
{maybe(() => params.ids.length)}
</strong>
) )
}} }}
/> />
@ -426,17 +387,10 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
); );
}} }}
</TypedProductBulkDeleteMutation> </TypedProductBulkDeleteMutation>
)}
</TypedCategoryBulkDeleteMutation>
</> </>
); );
}} }}
</TypedCategoryDetailsQuery> </TypedCategoryDetailsQuery>
); );
}}
</TypedCategoryUpdateMutation>
)}
</TypedCategoryDeleteMutation>
);
}; };
export default CategoryDetails; export default CategoryDetails;

View file

@ -18,7 +18,7 @@ import usePaginator, {
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import { CategoryListPage } from "../../components/CategoryListPage/CategoryListPage"; import { CategoryListPage } from "../../components/CategoryListPage/CategoryListPage";
import { TypedCategoryBulkDeleteMutation } from "../../mutations"; import { useCategoryBulkDeleteMutation } from "../../mutations";
import { TypedRootCategoriesQuery } from "../../queries"; import { TypedRootCategoriesQuery } from "../../queries";
import { CategoryBulkDelete } from "../../types/CategoryBulkDelete"; import { CategoryBulkDelete } from "../../types/CategoryBulkDelete";
import { import {
@ -138,17 +138,18 @@ export const CategoryList: React.FC<CategoryListProps> = ({ params }) => {
reset(); reset();
} }
}; };
return (
<TypedCategoryBulkDeleteMutation const [
onCompleted={handleCategoryBulkDelete} categoryBulkDelete,
> categoryBulkDeleteOpts
{(categoryBulkDelete, categoryBulkDeleteOpts) => { ] = useCategoryBulkDeleteMutation({
onCompleted: handleCategoryBulkDelete
});
const bulkDeleteState = getMutationState( const bulkDeleteState = getMutationState(
categoryBulkDeleteOpts.called, categoryBulkDeleteOpts.called,
categoryBulkDeleteOpts.loading, categoryBulkDeleteOpts.loading,
maybe( maybe(() => categoryBulkDeleteOpts.data.categoryBulkDelete.errors)
() => categoryBulkDeleteOpts.data.categoryBulkDelete.errors
)
); );
return ( return (
@ -251,9 +252,6 @@ export const CategoryList: React.FC<CategoryListProps> = ({ params }) => {
</> </>
); );
}} }}
</TypedCategoryBulkDeleteMutation>
);
}}
</TypedRootCategoriesQuery> </TypedRootCategoriesQuery>
); );
}; };

65
src/hooks/makeMutation.ts Normal file
View file

@ -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<TData, TVariables> = [
MutationFunction<TData, TVariables>,
MutationResult<TData>
];
type UseMutationCbs<TData> = Partial<{
onCompleted: (data: TData) => void;
onError: (error: ApolloError) => void;
}>;
type UseMutationHook<TData, TVariables> = (
cbs: UseMutationCbs<TData>
) => UseMutation<TData, TVariables>;
function makeMutation<TData, TVariables>(
mutation: DocumentNode
): UseMutationHook<TData, TVariables> {
function useMutation<TData, TVariables>({
onCompleted,
onError
}: UseMutationCbs<TData>): UseMutation<TData, TVariables> {
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;