Add rich text editor to category pages

This commit is contained in:
dominik-zeglen 2020-11-03 17:17:47 +01:00
parent 28cc74c954
commit 9adde24a48
5 changed files with 391 additions and 268 deletions

View file

@ -2,43 +2,23 @@ import AppHeader from "@saleor/components/AppHeader";
import { CardSpacer } from "@saleor/components/CardSpacer"; import { CardSpacer } from "@saleor/components/CardSpacer";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import Form from "@saleor/components/Form"; import Metadata from "@saleor/components/Metadata";
import Metadata, { MetadataFormData } from "@saleor/components/Metadata";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar"; import SaveButtonBar from "@saleor/components/SaveButtonBar";
import SeoForm from "@saleor/components/SeoForm"; import SeoForm from "@saleor/components/SeoForm";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment"; import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import { ContentState, convertToRaw, RawDraftContentState } from "draft-js";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import CategoryDetailsForm from "../../components/CategoryDetailsForm"; import CategoryDetailsForm from "../../components/CategoryDetailsForm";
import CategoryCreateForm, { CategoryCreateData } from "./form";
export interface FormData extends MetadataFormData {
description: RawDraftContentState;
name: string;
slug: string;
seoTitle: string;
seoDescription: string;
}
const initialData: FormData = {
description: convertToRaw(ContentState.createFromText("")),
metadata: [],
name: "",
privateMetadata: [],
seoDescription: "",
seoTitle: "",
slug: ""
};
export interface CategoryCreatePageProps { export interface CategoryCreatePageProps {
errors: ProductErrorFragment[]; errors: ProductErrorFragment[];
disabled: boolean; disabled: boolean;
saveButtonBarState: ConfirmButtonTransitionState; saveButtonBarState: ConfirmButtonTransitionState;
onSubmit(data: FormData); onSubmit(data: CategoryCreateData);
onBack(); onBack();
} }
@ -50,63 +30,57 @@ export const CategoryCreatePage: React.FC<CategoryCreatePageProps> = ({
saveButtonBarState saveButtonBarState
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const {
makeChangeHandler: makeMetadataChangeHandler
} = useMetadataChangeTrigger();
return ( return (
<Form onSubmit={onSubmit} initial={initialData} confirmLeave> <CategoryCreateForm onSubmit={onSubmit}>
{({ data, change, submit, hasChanged }) => { {({ data, change, handlers, submit, hasChanged }) => (
const changeMetadata = makeMetadataChangeHandler(change); <Container>
<AppHeader onBack={onBack}>
return ( {intl.formatMessage(sectionNames.categories)}
<Container> </AppHeader>
<AppHeader onBack={onBack}> <PageHeader
{intl.formatMessage(sectionNames.categories)} title={intl.formatMessage({
</AppHeader> defaultMessage: "Create New Category",
<PageHeader description: "page header"
title={intl.formatMessage({ })}
defaultMessage: "Create New Category", />
description: "page header" <div>
})} <CategoryDetailsForm
data={data}
disabled={disabled}
errors={errors}
onChange={change}
onDescriptionChange={handlers.changeDescription}
/> />
<div> <CardSpacer />
<CategoryDetailsForm <SeoForm
disabled={disabled} allowEmptySlug={true}
data={data} helperText={intl.formatMessage({
onChange={change} defaultMessage:
errors={errors} "Add search engine title and description to make this category easier to find"
/> })}
<CardSpacer /> slug={data.slug}
<SeoForm slugPlaceholder={data.name}
allowEmptySlug={true} title={data.seoTitle}
helperText={intl.formatMessage({ titlePlaceholder={data.name}
defaultMessage: description={data.seoDescription}
"Add search engine title and description to make this category easier to find" descriptionPlaceholder={data.name}
})} loading={disabled}
slug={data.slug} onChange={change}
slugPlaceholder={data.name} disabled={disabled}
title={data.seoTitle} />
titlePlaceholder={data.name} <CardSpacer />
description={data.seoDescription} <Metadata data={data} onChange={handlers.changeMetadata} />
descriptionPlaceholder={data.name} <SaveButtonBar
loading={disabled} onCancel={onBack}
onChange={change} onSave={submit}
disabled={disabled} state={saveButtonBarState}
/> disabled={disabled || !hasChanged}
<CardSpacer /> />
<Metadata data={data} onChange={changeMetadata} /> </div>
<SaveButtonBar </Container>
onCancel={onBack} )}
onSave={submit} </CategoryCreateForm>
state={saveButtonBarState}
disabled={disabled || !hasChanged}
/>
</div>
</Container>
);
}}
</Form>
); );
}; };
CategoryCreatePage.displayName = "CategoryCreatePage"; CategoryCreatePage.displayName = "CategoryCreatePage";

View file

@ -0,0 +1,100 @@
import { OutputData } from "@editorjs/editorjs";
import { MetadataFormData } from "@saleor/components/Metadata";
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
import useForm, { FormChange } from "@saleor/hooks/useForm";
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
import getMetadata from "@saleor/utils/metadata/getMetadata";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import useRichText from "@saleor/utils/richText/useRichText";
import React from "react";
export interface CategoryCreateFormData extends MetadataFormData {
name: string;
seoDescription: string;
seoTitle: string;
slug: string;
}
export interface CategoryCreateData extends CategoryCreateFormData {
description: OutputData;
}
interface CategoryCreateHandlers {
changeMetadata: FormChange;
changeDescription: RichTextEditorChange;
}
export interface UseCategoryCreateFormResult {
change: FormChange;
data: CategoryCreateData;
handlers: CategoryCreateHandlers;
hasChanged: boolean;
submit: () => Promise<boolean>;
}
export interface CategoryCreateFormProps {
children: (props: UseCategoryCreateFormResult) => React.ReactNode;
onSubmit: (data: CategoryCreateData) => Promise<any[]>;
}
function useCategoryCreateForm(
onSubmit: (data: CategoryCreateData) => Promise<any[]>
): UseCategoryCreateFormResult {
const [changed, setChanged] = React.useState(false);
const triggerChange = () => setChanged(true);
const form = useForm<CategoryCreateFormData>({
metadata: [],
name: "",
privateMetadata: [],
seoDescription: "",
seoTitle: "",
slug: ""
});
const [description, changeDescription] = useRichText({
initial: null,
triggerChange
});
const {
isMetadataModified,
isPrivateMetadataModified,
makeChangeHandler: makeMetadataChangeHandler
} = useMetadataChangeTrigger();
const handleChange: FormChange = (event, cb) => {
form.change(event, cb);
triggerChange();
};
const changeMetadata = makeMetadataChangeHandler(handleChange);
// Need to make it function to always have description.current up to date
const getData = (): CategoryCreateData => ({
...form.data,
...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified),
description: description.current
});
const submit = () => handleFormSubmit(getData(), onSubmit, setChanged);
return {
change: handleChange,
data: getData(),
handlers: {
changeDescription,
changeMetadata
},
hasChanged: changed,
submit
};
}
const CategoryCreateForm: React.FC<CategoryCreateFormProps> = ({
children,
onSubmit
}) => {
const props = useCategoryCreateForm(onSubmit);
return <form onSubmit={props.submit}>{children(props)}</form>;
};
CategoryCreateForm.displayName = "CategoryCreateForm";
export default CategoryCreateForm;

View file

@ -1,35 +1,34 @@
import { OutputData } from "@editorjs/editorjs";
import Card from "@material-ui/core/Card"; import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent"; import CardContent from "@material-ui/core/CardContent";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import RichTextEditor from "@saleor/components/RichTextEditor"; import RichTextEditor, {
RichTextEditorChange
} from "@saleor/components/RichTextEditor";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment"; import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors"; import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
import { RawDraftContentState } from "draft-js";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { maybe } from "../../../misc";
import { CategoryDetails_category } from "../../types/CategoryDetails";
interface CategoryDetailsFormProps { interface CategoryDetailsFormProps {
category?: CategoryDetails_category;
data: { data: {
name: string; name: string;
description: RawDraftContentState; description: OutputData;
}; };
disabled: boolean; disabled: boolean;
errors: ProductErrorFragment[]; errors: ProductErrorFragment[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
onDescriptionChange: RichTextEditorChange;
} }
export const CategoryDetailsForm: React.FC<CategoryDetailsFormProps> = ({ export const CategoryDetailsForm: React.FC<CategoryDetailsFormProps> = ({
category,
disabled, disabled,
data, data,
onChange, onChange,
onDescriptionChange,
errors errors
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@ -58,15 +57,15 @@ export const CategoryDetailsForm: React.FC<CategoryDetailsFormProps> = ({
</div> </div>
<FormSpacer /> <FormSpacer />
<RichTextEditor <RichTextEditor
data={data.description}
disabled={disabled} disabled={disabled}
error={!!formErrors.descriptionJson} error={!!formErrors.descriptionJson}
helperText={getProductErrorMessage(formErrors.descriptionJson, intl)} helperText={getProductErrorMessage(formErrors.descriptionJson, intl)}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Category Description" defaultMessage: "Category Description"
})} })}
initial={maybe(() => JSON.parse(category.descriptionJson))}
name="description" name="description"
onChange={onChange} onChange={onDescriptionChange}
/> />
</CardContent> </CardContent>
</Card> </Card>

View file

@ -5,9 +5,7 @@ import { CardSpacer } from "@saleor/components/CardSpacer";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import Form from "@saleor/components/Form";
import Metadata from "@saleor/components/Metadata/Metadata"; import Metadata from "@saleor/components/Metadata/Metadata";
import { MetadataFormData } from "@saleor/components/Metadata/types";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar"; import SaveButtonBar from "@saleor/components/SaveButtonBar";
import SeoForm from "@saleor/components/SeoForm"; import SeoForm from "@saleor/components/SeoForm";
@ -15,9 +13,6 @@ import { Tab, TabContainer } from "@saleor/components/Tab";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment"; import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
import { SubmitPromise } from "@saleor/hooks/useForm"; import { SubmitPromise } from "@saleor/hooks/useForm";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { mapMetadataItemToInput } from "@saleor/utils/maps";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import { RawDraftContentState } from "draft-js";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
@ -32,15 +27,7 @@ import {
} from "../../types/CategoryDetails"; } from "../../types/CategoryDetails";
import CategoryBackground from "../CategoryBackground"; import CategoryBackground from "../CategoryBackground";
import CategoryProducts from "../CategoryProducts"; import CategoryProducts from "../CategoryProducts";
import CategoryUpdateForm, { CategoryUpdateData } from "./form";
export interface FormData extends MetadataFormData {
backgroundImageAlt: string;
description: RawDraftContentState;
name: string;
slug: string;
seoTitle: string;
seoDescription: string;
}
export enum CategoryPageTab { export enum CategoryPageTab {
categories = "categories", categories = "categories",
@ -62,7 +49,7 @@ export interface CategoryUpdatePageProps
}; };
saveButtonBarState: ConfirmButtonTransitionState; saveButtonBarState: ConfirmButtonTransitionState;
onImageDelete: () => void; onImageDelete: () => void;
onSubmit: (data: FormData) => SubmitPromise; onSubmit: (data: CategoryUpdateData) => SubmitPromise;
onImageUpload(file: File); onImageUpload(file: File);
onNextPage(); onNextPage();
onPreviousPage(); onPreviousPage();
@ -106,180 +93,136 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
toggleAll toggleAll
}: CategoryUpdatePageProps) => { }: CategoryUpdatePageProps) => {
const intl = useIntl(); const intl = useIntl();
const {
isMetadataModified,
isPrivateMetadataModified,
makeChangeHandler: makeMetadataChangeHandler
} = useMetadataChangeTrigger();
const initialData: FormData = category
? {
backgroundImageAlt: maybe(() => category.backgroundImage.alt, ""),
description: maybe(() => JSON.parse(category.descriptionJson)),
metadata: category?.metadata?.map(mapMetadataItemToInput),
name: category.name || "",
privateMetadata: category?.privateMetadata?.map(mapMetadataItemToInput),
seoDescription: category.seoDescription || "",
seoTitle: category.seoTitle || "",
slug: category?.slug || ""
}
: {
backgroundImageAlt: "",
description: "",
metadata: undefined,
name: "",
privateMetadata: undefined,
seoDescription: "",
seoTitle: "",
slug: ""
};
const handleSubmit = (data: FormData) => {
const metadata = isMetadataModified ? data.metadata : undefined;
const privateMetadata = isPrivateMetadataModified
? data.privateMetadata
: undefined;
return onSubmit({
...data,
metadata,
privateMetadata
});
};
return ( return (
<Form onSubmit={handleSubmit} initial={initialData} confirmLeave> <CategoryUpdateForm category={category} onSubmit={onSubmit}>
{({ data, change, submit, hasChanged }) => { {({ data, change, handlers, submit, hasChanged }) => (
const changeMetadata = makeMetadataChangeHandler(change); <Container>
<AppHeader onBack={onBack}>
return ( {intl.formatMessage(sectionNames.categories)}
<Container> </AppHeader>
<AppHeader onBack={onBack}> <PageHeader title={category?.name} />
{intl.formatMessage(sectionNames.categories)} <CategoryDetailsForm
</AppHeader> data={data}
<PageHeader title={category ? category.name : undefined} /> disabled={disabled}
<CategoryDetailsForm errors={errors}
category={category} onChange={change}
data={data} onDescriptionChange={handlers.changeDescription}
disabled={disabled} />
errors={errors} <CardSpacer />
onChange={change} <CategoryBackground
/> data={data}
<CardSpacer /> onImageUpload={onImageUpload}
<CategoryBackground onImageDelete={onImageDelete}
data={data} image={maybe(() => category.backgroundImage)}
onImageUpload={onImageUpload} onChange={change}
onImageDelete={onImageDelete} />
image={maybe(() => category.backgroundImage)} <CardSpacer />
onChange={change} <SeoForm
/> helperText={intl.formatMessage({
<CardSpacer /> defaultMessage:
<SeoForm "Add search engine title and description to make this category easier to find"
helperText={intl.formatMessage({ })}
defaultMessage: errors={errors}
"Add search engine title and description to make this category easier to find" title={data.seoTitle}
})} titlePlaceholder={data.name}
errors={errors} description={data.seoDescription}
title={data.seoTitle} descriptionPlaceholder={data.name}
titlePlaceholder={data.name} slug={data.slug}
description={data.seoDescription} slugPlaceholder={data.name}
descriptionPlaceholder={data.name} loading={!category}
slug={data.slug} onChange={change}
slugPlaceholder={data.name} disabled={disabled}
loading={!category} />
onChange={change} <CardSpacer />
disabled={disabled} <Metadata data={data} onChange={handlers.changeMetadata} />
/> <CardSpacer />
<CardSpacer /> <TabContainer>
<Metadata data={data} onChange={changeMetadata} /> <CategoriesTab
<CardSpacer /> isActive={currentTab === CategoryPageTab.categories}
<TabContainer> changeTab={changeTab}
<CategoriesTab >
isActive={currentTab === CategoryPageTab.categories} <FormattedMessage
changeTab={changeTab} defaultMessage="Subcategories"
> description="number of subcategories in category"
<FormattedMessage />
defaultMessage="Subcategories" </CategoriesTab>
description="number of subcategories in category" <ProductsTab
/> isActive={currentTab === CategoryPageTab.products}
</CategoriesTab> changeTab={changeTab}
<ProductsTab >
isActive={currentTab === CategoryPageTab.products} <FormattedMessage
changeTab={changeTab} defaultMessage="Products"
> description="number of products in category"
<FormattedMessage />
defaultMessage="Products" </ProductsTab>
description="number of products in category" </TabContainer>
/> <CardSpacer />
</ProductsTab> {currentTab === CategoryPageTab.categories && (
</TabContainer> <Card>
<CardSpacer /> <CardTitle
{currentTab === CategoryPageTab.categories && ( title={intl.formatMessage({
<Card> defaultMessage: "All Subcategories",
<CardTitle description: "section header"
title={intl.formatMessage({ })}
defaultMessage: "All Subcategories", toolbar={
description: "section header" <Button
})} color="primary"
toolbar={ variant="text"
<Button onClick={onAddCategory}
color="primary" >
variant="text" <FormattedMessage
onClick={onAddCategory} defaultMessage="Create subcategory"
> description="button"
<FormattedMessage />
defaultMessage="Create subcategory" </Button>
description="button" }
/> />
</Button> <CategoryList
} categories={subcategories}
/>
<CategoryList
categories={subcategories}
disabled={disabled}
isChecked={isChecked}
isRoot={false}
pageInfo={pageInfo}
selected={selected}
sort={undefined}
toggle={toggle}
toggleAll={toggleAll}
toolbar={subcategoryListToolbar}
onNextPage={onNextPage}
onPreviousPage={onPreviousPage}
onRowClick={onCategoryClick}
onSort={() => undefined}
/>
</Card>
)}
{currentTab === CategoryPageTab.products && (
<CategoryProducts
categoryName={maybe(() => category.name)}
products={products}
disabled={disabled} disabled={disabled}
isChecked={isChecked}
isRoot={false}
pageInfo={pageInfo} pageInfo={pageInfo}
onNextPage={onNextPage} selected={selected}
onPreviousPage={onPreviousPage} sort={undefined}
onRowClick={onProductClick}
onAdd={onAddProduct}
toggle={toggle} toggle={toggle}
toggleAll={toggleAll} toggleAll={toggleAll}
selected={selected} toolbar={subcategoryListToolbar}
isChecked={isChecked} onNextPage={onNextPage}
toolbar={productListToolbar} onPreviousPage={onPreviousPage}
onRowClick={onCategoryClick}
onSort={() => undefined}
/> />
)} </Card>
<SaveButtonBar )}
onCancel={onBack} {currentTab === CategoryPageTab.products && (
onDelete={onDelete} <CategoryProducts
onSave={submit} categoryName={category?.name}
state={saveButtonBarState} products={products}
disabled={disabled || !hasChanged} disabled={disabled}
pageInfo={pageInfo}
onNextPage={onNextPage}
onPreviousPage={onPreviousPage}
onRowClick={onProductClick}
onAdd={onAddProduct}
toggle={toggle}
toggleAll={toggleAll}
selected={selected}
isChecked={isChecked}
toolbar={productListToolbar}
/> />
</Container> )}
); <SaveButtonBar
}} onCancel={onBack}
</Form> onDelete={onDelete}
onSave={submit}
state={saveButtonBarState}
disabled={disabled || !hasChanged}
/>
</Container>
)}
</CategoryUpdateForm>
); );
}; };
CategoryUpdatePage.displayName = "CategoryUpdatePage"; CategoryUpdatePage.displayName = "CategoryUpdatePage";

View file

@ -0,0 +1,107 @@
import { OutputData } from "@editorjs/editorjs";
import { CategoryDetails_category } from "@saleor/categories/types/CategoryDetails";
import { MetadataFormData } from "@saleor/components/Metadata";
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
import useForm, { FormChange } from "@saleor/hooks/useForm";
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
import { mapMetadataItemToInput } from "@saleor/utils/maps";
import getMetadata from "@saleor/utils/metadata/getMetadata";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import useRichText from "@saleor/utils/richText/useRichText";
import React from "react";
export interface CategoryUpdateFormData extends MetadataFormData {
backgroundImageAlt: string;
name: string;
slug: string;
seoTitle: string;
seoDescription: string;
}
export interface CategoryUpdateData extends CategoryUpdateFormData {
description: OutputData;
}
interface CategoryUpdateHandlers {
changeMetadata: FormChange;
changeDescription: RichTextEditorChange;
}
export interface UseCategoryUpdateFormResult {
change: FormChange;
data: CategoryUpdateData;
handlers: CategoryUpdateHandlers;
hasChanged: boolean;
submit: () => Promise<boolean>;
}
export interface CategoryUpdateFormProps {
children: (props: UseCategoryUpdateFormResult) => React.ReactNode;
category: CategoryDetails_category;
onSubmit: (data: CategoryUpdateData) => Promise<any[]>;
}
function useCategoryUpdateForm(
category: CategoryDetails_category,
onSubmit: (data: CategoryUpdateData) => Promise<any[]>
): UseCategoryUpdateFormResult {
const [changed, setChanged] = React.useState(false);
const triggerChange = () => setChanged(true);
const form = useForm<CategoryUpdateFormData>({
backgroundImageAlt: category?.backgroundImage?.alt || "",
metadata: category?.metadata?.map(mapMetadataItemToInput),
name: category?.name || "",
privateMetadata: category?.privateMetadata?.map(mapMetadataItemToInput),
seoDescription: category?.seoDescription || "",
seoTitle: category?.seoTitle || "",
slug: category?.slug || ""
});
const [description, changeDescription] = useRichText({
initial: category?.descriptionJson,
triggerChange
});
const {
isMetadataModified,
isPrivateMetadataModified,
makeChangeHandler: makeMetadataChangeHandler
} = useMetadataChangeTrigger();
const handleChange: FormChange = (event, cb) => {
form.change(event, cb);
triggerChange();
};
const changeMetadata = makeMetadataChangeHandler(handleChange);
// Need to make it function to always have description.current up to date
const getData = (): CategoryUpdateData => ({
...form.data,
...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified),
description: description.current
});
const submit = () => handleFormSubmit(getData(), onSubmit, setChanged);
return {
change: handleChange,
data: getData(),
handlers: {
changeDescription,
changeMetadata
},
hasChanged: changed,
submit
};
}
const CategoryUpdateForm: React.FC<CategoryUpdateFormProps> = ({
children,
category,
onSubmit
}) => {
const props = useCategoryUpdateForm(category, onSubmit);
return <form onSubmit={props.submit}>{children(props)}</form>;
};
CategoryUpdateForm.displayName = "CategoryUpdateForm";
export default CategoryUpdateForm;