From 437df6fe9a11dacb7f8b098672b385244864ce45 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 3 Nov 2020 14:53:17 +0100 Subject: [PATCH] wip --- .../RichTextEditor/RichTextEditor.tsx | 8 ++- .../ProductCreatePage/ProductCreatePage.tsx | 9 +-- .../components/ProductCreatePage/form.tsx | 53 ++++++++------ .../components/ProductUpdatePage/form.tsx | 18 ++--- src/utils/richText/useRichText.test.ts | 72 +++++++++++++++++++ src/utils/richText/useRichText.ts | 28 ++++++++ 6 files changed, 146 insertions(+), 42 deletions(-) create mode 100644 src/utils/richText/useRichText.test.ts create mode 100644 src/utils/richText/useRichText.ts diff --git a/src/components/RichTextEditor/RichTextEditor.tsx b/src/components/RichTextEditor/RichTextEditor.tsx index 8653a9616..2eb58bd04 100644 --- a/src/components/RichTextEditor/RichTextEditor.tsx +++ b/src/components/RichTextEditor/RichTextEditor.tsx @@ -19,6 +19,7 @@ export interface RichTextEditorProps { label: string; name: string; onChange: RichTextEditorChange; + onReady?: () => void; } const useStyles = makeStyles( @@ -86,7 +87,10 @@ const useStyles = makeStyles( border: `1px solid ${fade(theme.palette.text.secondary, 0.4)}`, borderRadius: 4, boxShadow: `inset 0 0 0 0 ${theme.palette.primary.main}`, + fontSize: theme.typography.body1.fontSize, padding: theme.spacing(3, 2), + paddingBottom: theme.spacing(), + paddingLeft: 10, position: "relative", transition: theme.transitions.duration.short + "ms" }, @@ -103,7 +107,8 @@ const RichTextEditor: React.FC = ({ error, helperText, label, - onChange + onChange, + onReady }) => { const classes = useStyles({}); @@ -120,6 +125,7 @@ const RichTextEditor: React.FC = ({ const savedData = await api.saver.save(); onChange(savedData); }, + onReady, tools: { header: { class: Header, diff --git a/src/products/components/ProductCreatePage/ProductCreatePage.tsx b/src/products/components/ProductCreatePage/ProductCreatePage.tsx index ffebe8145..b288068b4 100644 --- a/src/products/components/ProductCreatePage/ProductCreatePage.tsx +++ b/src/products/components/ProductCreatePage/ProductCreatePage.tsx @@ -86,12 +86,6 @@ export const ProductCreatePage: React.FC = ({ const intl = useIntl(); const localizeDate = useDateLocalize(); - // Ensures that it will not change after component rerenders, because it - // generates different block keys and it causes editor to lose its content. - const initialDescription = React.useRef( - convertToRaw(ContentState.createFromText("")) - ); - // Display values const [selectedCategory, setSelectedCategory] = useStateFromProps( initial?.category || "" @@ -144,8 +138,8 @@ export const ProductCreatePage: React.FC = ({ data={data} disabled={disabled} errors={errors} - initialDescription={initialDescription.current} onChange={change} + onDescriptionChange={handlers.changeDescription} /> {data.attributes.length > 0 && ( @@ -167,6 +161,7 @@ export const ProductCreatePage: React.FC = ({ weightUnit={weightUnit} onChange={change} /> + & - Record< - "changeStock" | "selectAttribute" | "selectAttributeMultiple", - FormsetChange - > & - Record<"addStock" | "deleteStock", (id: string) => void>; +interface ProductCreateHandlers + extends Record< + | "changeMetadata" + | "selectCategory" + | "selectCollection" + | "selectProductType" + | "selectTaxRate", + FormChange + >, + Record< + "changeStock" | "selectAttribute" | "selectAttributeMultiple", + FormsetChange + >, + Record<"addStock" | "deleteStock", (id: string) => void> { + changeDescription: RichTextEditorChange; +} export interface UseProductCreateFormResult { change: FormChange; data: ProductCreateData; @@ -106,7 +111,7 @@ const defaultInitialFormData: ProductCreateFormData & changeTaxCode: false, chargeTaxes: false, collections: [], - description: {} as any, + description: null, isAvailable: false, isAvailableForPurchase: false, isPublished: false, @@ -117,7 +122,7 @@ const defaultInitialFormData: ProductCreateFormData & publicationDate: "", seoDescription: "", seoTitle: "", - sku: null, + sku: "", slug: "", stockQuantity: null, taxCode: null, @@ -152,6 +157,10 @@ function useProductCreateForm( const [productType, setProductType] = useStateFromProps( initialProductType || null ); + const [description, changeDescription] = useRichText({ + initial: null, + triggerChange + }); const { makeChangeHandler: makeMetadataChangeHandler @@ -211,19 +220,21 @@ function useProductCreateForm( ); const changeMetadata = makeMetadataChangeHandler(handleChange); - const data: ProductCreateData = { + const getData = (): ProductCreateData => ({ ...form.data, attributes: attributes.data, + description: description.current, productType, stocks: stocks.data - }; - const submit = () => onSubmit(data); + }); + const submit = () => onSubmit(getData()); return { change: handleChange, - data, + data: getData(), handlers: { addStock: handleStockAdd, + changeDescription, changeMetadata, changeStock: handleStockChange, deleteStock: handleStockDelete, diff --git a/src/products/components/ProductUpdatePage/form.tsx b/src/products/components/ProductUpdatePage/form.tsx index ff4c40393..c1f2e8fa2 100644 --- a/src/products/components/ProductUpdatePage/form.tsx +++ b/src/products/components/ProductUpdatePage/form.tsx @@ -23,6 +23,7 @@ import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit"; import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler"; import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger"; +import useRichText from "@saleor/utils/richText/useRichText"; import { diff } from "fast-array-diff"; import React from "react"; @@ -164,15 +165,10 @@ function useProductUpdateForm( ); const attributes = useFormset(getAttributeInputFromProduct(product)); const stocks = useFormset(getStockInputFromProduct(product)); - const description = React.useRef(); - - React.useEffect(() => { - try { - description.current = JSON.parse(product.descriptionJson); - } catch { - description.current = undefined; - } - }, [product]); + const [description, changeDescription] = useRichText({ + initial: product?.descriptionJson, + triggerChange + }); const { isMetadataModified, @@ -227,10 +223,6 @@ function useProductUpdateForm( opts.taxTypes ); const changeMetadata = makeMetadataChangeHandler(handleChange); - const changeDescription: RichTextEditorChange = data => { - triggerChange(); - description.current = data; - }; const data: ProductUpdateData = { ...form.data, diff --git a/src/utils/richText/useRichText.test.ts b/src/utils/richText/useRichText.test.ts new file mode 100644 index 000000000..26fc9c782 --- /dev/null +++ b/src/utils/richText/useRichText.test.ts @@ -0,0 +1,72 @@ +import { OutputData } from "@editorjs/editorjs"; +import { renderHook } from "@testing-library/react-hooks"; + +import useRichText from "./useRichText"; + +type Fixtures = Record<"short" | "long", OutputData>; +const fixtures: Fixtures = { + long: { + blocks: [ + { + data: { + level: 1, + text: "Some header" + }, + type: "header" + }, + { + data: { + text: "Some text" + }, + type: "paragraph" + } + ] + }, + short: { + blocks: [ + { + data: { + text: "Some text" + }, + type: "paragraph" + } + ] + } +}; + +describe("useRichText", () => { + it("properly saves data in form", () => { + const triggerChange = jest.fn(); + const hook = renderHook(() => + useRichText({ + triggerChange + }) + ); + + const [data, change] = hook.result.current; + expect(data.current).toBe(undefined); + + change(fixtures.short); + + expect(data.current).toMatchObject(fixtures.short); + expect(triggerChange).toHaveBeenCalled(); + }); + + it("properly updates data in form", () => { + const triggerChange = jest.fn(); + const hook = renderHook(() => + useRichText({ + initial: JSON.stringify(fixtures.short), + triggerChange + }) + ); + + const [data, change] = hook.result.current; + expect(data.current).toMatchObject(fixtures.short); + + change(fixtures.long); + + expect(data.current).toMatchObject(fixtures.long); + expect(triggerChange).toHaveBeenCalled(); + }); +}); diff --git a/src/utils/richText/useRichText.ts b/src/utils/richText/useRichText.ts new file mode 100644 index 000000000..58613193b --- /dev/null +++ b/src/utils/richText/useRichText.ts @@ -0,0 +1,28 @@ +import { OutputData } from "@editorjs/editorjs"; +import { RichTextEditorChange } from "@saleor/components/RichTextEditor"; +import { MutableRefObject, useEffect, useRef } from "react"; + +function useRichText(opts: { + initial?: string | null; + triggerChange: () => void; +}): [MutableRefObject, RichTextEditorChange] { + const data = useRef( + opts.initial === null ? { blocks: [] } : undefined + ); + useEffect(() => { + try { + data.current = JSON.parse(opts.initial); + } catch { + data.current = undefined; + } + }, [opts.initial]); + + const change: RichTextEditorChange = newData => { + opts.triggerChange(); + data.current = newData; + }; + + return [data, change]; +} + +export default useRichText;