Handle case when attribute value is empty on initial render (#2068)
* Handle case when attribute value is empty on initial render * Fix useRichText when initial data is undefined * Update snapshots * Handle loading state in useRichText * Add placeholder RichTextEditor when data is loading * Update snapshots
This commit is contained in:
parent
7df8268101
commit
47ec01dd7c
17 changed files with 183 additions and 10 deletions
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "saleor-dashboard",
|
||||
"version": "3.1.0-dev",
|
||||
"version": "3.4.0-dev",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Card, CardContent, TextField } from "@material-ui/core";
|
|||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import RichTextEditor from "@saleor/components/RichTextEditor";
|
||||
import { RichTextEditorLoading } from "@saleor/components/RichTextEditor/RichTextEditorLoading";
|
||||
import { ProductErrorFragment } from "@saleor/graphql";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
|
||||
|
@ -58,7 +59,7 @@ export const CategoryDetailsForm: React.FC<CategoryDetailsFormProps> = ({
|
|||
/>
|
||||
</div>
|
||||
<FormSpacer />
|
||||
{isReadyForMount && (
|
||||
{isReadyForMount ? (
|
||||
<RichTextEditor
|
||||
defaultValue={defaultValue}
|
||||
editorRef={editorRef}
|
||||
|
@ -72,6 +73,14 @@ export const CategoryDetailsForm: React.FC<CategoryDetailsFormProps> = ({
|
|||
})}
|
||||
name="description"
|
||||
/>
|
||||
) : (
|
||||
<RichTextEditorLoading
|
||||
label={intl.formatMessage({
|
||||
id: "8HRy+U",
|
||||
defaultMessage: "Category Description"
|
||||
})}
|
||||
name="description"
|
||||
/>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
@ -78,6 +78,7 @@ function useCategoryUpdateForm(
|
|||
|
||||
const richText = useRichText({
|
||||
initial: category?.description,
|
||||
loading: !category,
|
||||
triggerChange
|
||||
});
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Card, CardContent, TextField } from "@material-ui/core";
|
|||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import RichTextEditor from "@saleor/components/RichTextEditor";
|
||||
import { RichTextEditorLoading } from "@saleor/components/RichTextEditor/RichTextEditorLoading";
|
||||
import { CollectionErrorFragment } from "@saleor/graphql";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
|
||||
|
@ -56,7 +57,7 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
fullWidth
|
||||
/>
|
||||
<FormSpacer />
|
||||
{isReadyForMount && (
|
||||
{isReadyForMount ? (
|
||||
<RichTextEditor
|
||||
defaultValue={defaultValue}
|
||||
editorRef={editorRef}
|
||||
|
@ -67,6 +68,11 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
name="description"
|
||||
disabled={disabled}
|
||||
/>
|
||||
) : (
|
||||
<RichTextEditorLoading
|
||||
label={intl.formatMessage(commonMessages.description)}
|
||||
name="description"
|
||||
/>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
@ -96,6 +96,7 @@ function useCollectionUpdateForm(
|
|||
|
||||
const richText = useRichText({
|
||||
initial: collection?.description,
|
||||
loading: !collection,
|
||||
triggerChange
|
||||
});
|
||||
|
||||
|
|
28
src/components/RichTextEditor/RichTextEditorLoading.tsx
Normal file
28
src/components/RichTextEditor/RichTextEditorLoading.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import React from "react";
|
||||
|
||||
import RichTextEditor, { RichTextEditorProps } from "./RichTextEditor";
|
||||
|
||||
interface RichTextEditorLoadingProps
|
||||
extends Omit<
|
||||
RichTextEditorProps,
|
||||
| "disabled"
|
||||
| "editorRef"
|
||||
| "onChange"
|
||||
| "defaultValue"
|
||||
| "error"
|
||||
| "helperText"
|
||||
> {
|
||||
helperText?: RichTextEditorProps["helperText"];
|
||||
}
|
||||
|
||||
export const RichTextEditorLoading = (props: RichTextEditorLoadingProps) => (
|
||||
<RichTextEditor
|
||||
{...props}
|
||||
disabled={true}
|
||||
readOnly={true}
|
||||
error={null}
|
||||
helperText={props.helperText ?? ""}
|
||||
defaultValue={{ blocks: [] }}
|
||||
editorRef={{ current: null }}
|
||||
/>
|
||||
);
|
|
@ -163,6 +163,7 @@ function usePageForm(
|
|||
|
||||
const richText = useRichText({
|
||||
initial: pageExists ? page?.content : null,
|
||||
loading: pageExists ? !page : false,
|
||||
triggerChange
|
||||
});
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Card, CardContent, TextField } from "@material-ui/core";
|
|||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import RichTextEditor from "@saleor/components/RichTextEditor";
|
||||
import { RichTextEditorLoading } from "@saleor/components/RichTextEditor/RichTextEditorLoading";
|
||||
import { PageErrorFragment } from "@saleor/graphql";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
|
@ -64,7 +65,7 @@ const PageInfo: React.FC<PageInfoProps> = props => {
|
|||
onChange={onChange}
|
||||
/>
|
||||
<FormSpacer />
|
||||
{isReadyForMount && (
|
||||
{isReadyForMount ? (
|
||||
<RichTextEditor
|
||||
defaultValue={defaultValue}
|
||||
editorRef={editorRef}
|
||||
|
@ -79,6 +80,15 @@ const PageInfo: React.FC<PageInfoProps> = props => {
|
|||
})}
|
||||
name={"content" as keyof PageData}
|
||||
/>
|
||||
) : (
|
||||
<RichTextEditorLoading
|
||||
label={intl.formatMessage({
|
||||
id: "gMwpNC",
|
||||
defaultMessage: "Content",
|
||||
description: "page content"
|
||||
})}
|
||||
name={"content" as keyof PageData}
|
||||
/>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
@ -5,6 +5,7 @@ import FormSpacer from "@saleor/components/FormSpacer";
|
|||
import Grid from "@saleor/components/Grid";
|
||||
import Hr from "@saleor/components/Hr";
|
||||
import RichTextEditor from "@saleor/components/RichTextEditor";
|
||||
import { RichTextEditorLoading } from "@saleor/components/RichTextEditor/RichTextEditorLoading";
|
||||
import { ProductErrorFragment } from "@saleor/graphql";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
|
||||
|
@ -60,7 +61,7 @@ export const ProductDetailsForm: React.FC<ProductDetailsFormProps> = ({
|
|||
onChange={onChange}
|
||||
/>
|
||||
<FormSpacer />
|
||||
{isReadyForMount && (
|
||||
{isReadyForMount ? (
|
||||
<RichTextEditor
|
||||
editorRef={editorRef}
|
||||
defaultValue={defaultValue}
|
||||
|
@ -71,6 +72,11 @@ export const ProductDetailsForm: React.FC<ProductDetailsFormProps> = ({
|
|||
label={intl.formatMessage(commonMessages.description)}
|
||||
name="description"
|
||||
/>
|
||||
) : (
|
||||
<RichTextEditorLoading
|
||||
label={intl.formatMessage(commonMessages.description)}
|
||||
name="description"
|
||||
/>
|
||||
)}
|
||||
<FormSpacer />
|
||||
<Hr />
|
||||
|
|
|
@ -281,6 +281,7 @@ function useProductUpdateForm(
|
|||
const stocks = useFormset(getStockInputFromProduct(product));
|
||||
const richText = useRichText({
|
||||
initial: product?.description,
|
||||
loading: !product,
|
||||
triggerChange
|
||||
});
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Card, CardContent, TextField } from "@material-ui/core";
|
|||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import RichTextEditor from "@saleor/components/RichTextEditor";
|
||||
import { RichTextEditorLoading } from "@saleor/components/RichTextEditor/RichTextEditorLoading";
|
||||
import { ShippingErrorFragment } from "@saleor/graphql";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
|
@ -100,7 +101,7 @@ const ShippingRateInfo: React.FC<ShippingRateInfoProps> = props => {
|
|||
onChange={onChange}
|
||||
/>
|
||||
<CardSpacer />
|
||||
{isReadyForMount && (
|
||||
{isReadyForMount ? (
|
||||
<RichTextEditor
|
||||
defaultValue={defaultValue}
|
||||
editorRef={editorRef}
|
||||
|
@ -111,6 +112,11 @@ const ShippingRateInfo: React.FC<ShippingRateInfoProps> = props => {
|
|||
label={intl.formatMessage(messages.description)}
|
||||
name="description"
|
||||
/>
|
||||
) : (
|
||||
<RichTextEditorLoading
|
||||
label={intl.formatMessage(messages.description)}
|
||||
name="description"
|
||||
/>
|
||||
)}
|
||||
<CardSpacer />
|
||||
<div className={classes.deliveryTimeFields}>
|
||||
|
|
|
@ -130,6 +130,7 @@ export const ShippingZoneRatesPage: React.FC<ShippingZoneRatesPageProps> = ({
|
|||
|
||||
const richText = useRichText({
|
||||
initial: rate?.description,
|
||||
loading: !rate,
|
||||
triggerChange
|
||||
});
|
||||
|
||||
|
|
|
@ -41756,6 +41756,20 @@ exports[`Storyshots Views / Categories / Update category loading 1`] = `
|
|||
<div
|
||||
class="FormSpacer-spacer-id"
|
||||
/>
|
||||
<div
|
||||
class="MuiFormControl-root-id MuiFormControl-fullWidth-id"
|
||||
data-test-id="rich-text-editor-description"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-shrink-id MuiInputLabel-outlined-id MuiFormLabel-disabled-id MuiInputLabel-disabled-id MuiFormLabel-focused-id MuiInputLabel-focused-id"
|
||||
data-shrink="true"
|
||||
>
|
||||
Category Description
|
||||
</label>
|
||||
<p
|
||||
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-disabled-id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -54202,6 +54216,20 @@ exports[`Storyshots Views / Collections / Collection detailsCollection details l
|
|||
<div
|
||||
class="FormSpacer-spacer-id"
|
||||
/>
|
||||
<div
|
||||
class="MuiFormControl-root-id MuiFormControl-fullWidth-id"
|
||||
data-test-id="rich-text-editor-description"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-shrink-id MuiInputLabel-outlined-id MuiFormLabel-disabled-id MuiInputLabel-disabled-id MuiFormLabel-focused-id MuiInputLabel-focused-id"
|
||||
data-shrink="true"
|
||||
>
|
||||
Description
|
||||
</label>
|
||||
<p
|
||||
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-disabled-id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -155768,6 +155796,20 @@ exports[`Storyshots Views / Pages / Page details loading 1`] = `
|
|||
<div
|
||||
class="FormSpacer-spacer-id"
|
||||
/>
|
||||
<div
|
||||
class="MuiFormControl-root-id MuiFormControl-fullWidth-id"
|
||||
data-test-id="rich-text-editor-content"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-shrink-id MuiInputLabel-outlined-id MuiFormLabel-disabled-id MuiInputLabel-disabled-id MuiFormLabel-focused-id MuiInputLabel-focused-id"
|
||||
data-shrink="true"
|
||||
>
|
||||
Content
|
||||
</label>
|
||||
<p
|
||||
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-disabled-id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -202808,6 +202850,20 @@ exports[`Storyshots Views / Products / Product edit when loading data 1`] = `
|
|||
<div
|
||||
class="FormSpacer-spacer-id"
|
||||
/>
|
||||
<div
|
||||
class="MuiFormControl-root-id MuiFormControl-fullWidth-id"
|
||||
data-test-id="rich-text-editor-description"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-shrink-id MuiInputLabel-outlined-id MuiFormLabel-disabled-id MuiInputLabel-disabled-id MuiFormLabel-focused-id MuiInputLabel-focused-id"
|
||||
data-shrink="true"
|
||||
>
|
||||
Description
|
||||
</label>
|
||||
<p
|
||||
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-disabled-id"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="FormSpacer-spacer-id"
|
||||
/>
|
||||
|
@ -233540,6 +233596,20 @@ exports[`Storyshots Views / Shipping / Shipping rate loading 1`] = `
|
|||
<div
|
||||
class="CardSpacer-spacer-id"
|
||||
/>
|
||||
<div
|
||||
class="MuiFormControl-root-id MuiFormControl-fullWidth-id"
|
||||
data-test-id="rich-text-editor-description"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-shrink-id MuiInputLabel-outlined-id MuiFormLabel-disabled-id MuiInputLabel-disabled-id MuiFormLabel-focused-id MuiInputLabel-focused-id"
|
||||
data-shrink="true"
|
||||
>
|
||||
Shipping Rate Description
|
||||
</label>
|
||||
<p
|
||||
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-disabled-id"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="CardSpacer-spacer-id"
|
||||
/>
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Typography } from "@material-ui/core";
|
|||
import { useExitFormDialog } from "@saleor/components/Form/useExitFormDialog";
|
||||
import RichTextEditor from "@saleor/components/RichTextEditor";
|
||||
import RichTextEditorContent from "@saleor/components/RichTextEditor/RichTextEditorContent";
|
||||
import { RichTextEditorLoading } from "@saleor/components/RichTextEditor/RichTextEditorLoading";
|
||||
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||
import useRichText from "@saleor/utils/richText/useRichText";
|
||||
|
@ -51,7 +52,7 @@ const TranslationFieldsRich: React.FC<TranslationFieldsRichProps> = ({
|
|||
|
||||
return edit ? (
|
||||
<form onSubmit={submit}>
|
||||
{isReadyForMount && (
|
||||
{isReadyForMount ? (
|
||||
<RichTextEditor
|
||||
defaultValue={defaultValue}
|
||||
editorRef={editorRef}
|
||||
|
@ -66,6 +67,15 @@ const TranslationFieldsRich: React.FC<TranslationFieldsRichProps> = ({
|
|||
name="translation"
|
||||
data-test-id="translation-field"
|
||||
/>
|
||||
) : (
|
||||
<RichTextEditorLoading
|
||||
label={intl.formatMessage({
|
||||
id: "/vCXIP",
|
||||
defaultMessage: "Translation"
|
||||
})}
|
||||
name="translation"
|
||||
data-test-id="translation-field"
|
||||
/>
|
||||
)}
|
||||
<TranslationFieldsSave
|
||||
saveButtonState={saveButtonState}
|
||||
|
|
|
@ -41,6 +41,11 @@ export const useMultipleRichText = <TKey extends string>({
|
|||
|
||||
const getDefaultValue = useCallback(
|
||||
(id: TKey) => {
|
||||
if (initial[id] === undefined) {
|
||||
setShouldMountById(id, true);
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
const result = JSON.parse(initial[id]);
|
||||
setShouldMountById(id, true);
|
||||
|
|
|
@ -23,13 +23,15 @@ describe("useRichText", () => {
|
|||
it("properly informs RichTextEditor when data is ready to mount", () => {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let initial: string | undefined;
|
||||
let loading = true;
|
||||
const { result, rerender } = renderHook(() =>
|
||||
useRichText({ initial, triggerChange })
|
||||
useRichText({ initial, loading, triggerChange })
|
||||
);
|
||||
|
||||
expect(result.current.isReadyForMount).toBe(false);
|
||||
|
||||
initial = JSON.stringify(fixtures.short); // for JSON.parse()
|
||||
loading = false;
|
||||
rerender();
|
||||
|
||||
expect(result.current.defaultValue).toStrictEqual(fixtures.short);
|
||||
|
@ -39,13 +41,15 @@ describe("useRichText", () => {
|
|||
it("returns undefined when JSON cannot be parsed", () => {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let initial: string | undefined;
|
||||
let loading = true;
|
||||
const { result, rerender } = renderHook(() =>
|
||||
useRichText({ initial, triggerChange })
|
||||
useRichText({ initial, loading, triggerChange })
|
||||
);
|
||||
|
||||
expect(result.current.isReadyForMount).toBe(false);
|
||||
|
||||
initial = "this-isnt-valid-json";
|
||||
loading = false;
|
||||
rerender();
|
||||
|
||||
expect(result.current.defaultValue).toBe(undefined);
|
||||
|
|
|
@ -4,10 +4,15 @@ import { useMemo, useRef, useState } from "react";
|
|||
|
||||
interface UseRichTextOptions {
|
||||
initial: string | null;
|
||||
loading?: boolean;
|
||||
triggerChange: () => void;
|
||||
}
|
||||
|
||||
export function useRichText({ initial, triggerChange }: UseRichTextOptions) {
|
||||
export function useRichText({
|
||||
initial,
|
||||
loading,
|
||||
triggerChange
|
||||
}: UseRichTextOptions) {
|
||||
const editorRef = useRef<EditorCore>(null);
|
||||
const [isReadyForMount, setIsReadyForMount] = useState(false);
|
||||
|
||||
|
@ -24,6 +29,15 @@ export function useRichText({ initial, triggerChange }: UseRichTextOptions) {
|
|||
};
|
||||
|
||||
const defaultValue = useMemo<OutputData | undefined>(() => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (initial === undefined) {
|
||||
setIsReadyForMount(true);
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
const result = JSON.parse(initial);
|
||||
setIsReadyForMount(true);
|
||||
|
|
Loading…
Reference in a new issue