Skip to content

Commit

Permalink
fix: 🐛 Fix re-render infinite loop
Browse files Browse the repository at this point in the history
  • Loading branch information
Pauline Didier committed Aug 13, 2024
1 parent 5263dc2 commit a0d745e
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 186 deletions.
95 changes: 0 additions & 95 deletions app/src/components/model/panels/DescriptionPreview.js

This file was deleted.

171 changes: 171 additions & 0 deletions app/src/components/model/panels/EditableDescription.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { marked } from "marked";
import DOMPurify from "dompurify";
import { useState, useEffect, useCallback } from "react";
import { Box, TextField, Typography } from "@mui/material";

marked.use({
extensions: [
{
name: "heading",
renderer({ text, depth }) {
return `<h${depth + 1}>${text}</h${depth + 1}>`;
},
},
{
name: "image",
renderer(_) {
return "Images are not supported";
},
},
{
name: "link",
renderer(token) {
if (!token.href) {
return `<p><strong>${token.text}</strong> (link without href)</p>`;
}
return `<a href="${token.href}" target="_blank" rel="noopener noreferrer">${token.text}</a>`;
},
},
{
name: "code",
renderer(token) {
if (!token.text) {
return "";
}
if (token.lang) {
return `<div><p>\`\`\`${token.lang}</p><pre style="margin-left:1em"><code class="language-${token.lang}">${token.text}</code></pre><p>\`\`\`</p></div>`;
}
return `<div><p>\`\`\`</p><pre style="margin-left:1em"><code class="language-${token.lang}">${token.text}</code></pre><p>\`\`\`</p></div>`;
},
},
{
name: "list",
renderer(token) {
const itemList = token.items.map((i) => {
return (
"<li style='margin-bottom:0'>" + i.raw.replace(/\n+$/, "") + "</li>"
);
});
if (token.ordered) {
return `<ol>${itemList.join("\n")}</ol>`;
} else {
return `<ul>${itemList.join("\n")}</ul>`;
}
},
},
],
});

const domPurityConfig = {
USE_PROFILES: { html: true },
FORBID_TAGS: ["img"],
ADD_ATTR: ["target"],
};

export const DescriptionPreview = ({
description,
handleOnClick = null,
showTitle = false,
sx = {},
}) => {
if (!description) {
handleOnClick(true);
return null;
}
const sanitizedHtml = DOMPurify.sanitize(
marked.parse(description),
domPurityConfig
);

if (handleOnClick) {
return (
<>
{showTitle && (
<Typography variant="body1" color="text.secondary">
Description
</Typography>
)}
<Box
dangerouslySetInnerHTML={{ __html: sanitizedHtml }}
onClick={handleOnClick}
sx={{ ...sx }}
></Box>
</>
);
} else {
return (
<>
{showTitle && <Typography variant="body1">Description</Typography>}
<Box
dangerouslySetInnerHTML={{ __html: sanitizedHtml }}
sx={{ ...sx }}
></Box>
</>
);
}
};

export const EditableDescription = ({
description,
readOnly = false,
updateDescription,
placeholder = "",
showPreviewTitle = false,
previewSx = {},
}) => {
const [isEditing, setIsEditing] = useState(readOnly ? false : !description);
const [value, setValue] = useState(description);

const descriptionInput = useCallback((inputElement) => {
if (inputElement) {
inputElement.focus();
}
}, []);

useEffect(() => {
setValue(description);
}, [description]);

const handleOnBlur = (e) => {
updateDescription(e.target.value);
setIsEditing(false);
};

const handleOnKeyDown = (e) => {
if ((!e.shiftKey && e.key === "Enter") || e.key === "Escape") {
e.preventDefault();
e.target.blur();
}
};

if (readOnly) {
return <DescriptionPreview description={description} />;
}

if (isEditing) {
return (
<TextField
fullWidth
multiline
variant="standard"
label="Description"
placeholder={placeholder}
disabled={readOnly}
value={value}
onBlur={(e) => handleOnBlur(e)}
onChange={(e) => setValue(e.target.value)}
onKeyDown={(e) => handleOnKeyDown(e)}
inputRef={descriptionInput}
/>
);
} else {
return (
<DescriptionPreview
description={description ? description : "Click to add a description"}
handleOnClick={() => setIsEditing(true)}
showTitle={showPreviewTitle}
sx={previewSx}
/>
);
}
};
71 changes: 12 additions & 59 deletions app/src/components/model/panels/left/ComponentTab.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@ import {
TextField,
Typography,
} from "@mui/material";
import { useEffect, useState, useRef } from "react";
import { useEffect, useState, useRef, useCallback } from "react";
import { useDispatch } from "react-redux";
import { patchComponent } from "../../../../actions/model/patchComponent";
import { useReadOnly } from "../../../../hooks/useReadOnly";
import { MultipleSystemsDropdown } from "../../../elements/MultipleSystemsDropdown";
import { COMPONENT_TYPE } from "../../board/constants";
import { useSelectedComponent } from "../../hooks/useSelectedComponent";
import { TechStacksDropdown } from "./TechStackDropdown";
import { DescriptionPreview } from "../DescriptionPreview";
import { BorderBottom } from "@mui/icons-material";
import { EditableDescription } from "../EditableDescription";

export function ComponentTab() {
const dispatch = useDispatch();
Expand All @@ -26,15 +25,6 @@ export function ComponentTab() {
const { type, classes, systems } = component;
const [name, setName] = useState(component.name);
const [description, setDescription] = useState(component.description || "");
const [isEditing, setIsEditing] = useState(
component.description === "" || !readOnly
);
const descriptionTextFieldRef = useRef(null);

function showDescriptionTextField() {
setIsEditing(true);
setTimeout(() => descriptionTextFieldRef.current.focus(), 2); // Time out needed for the ref to be set
}
// const [type, setType] = useState(component.type);
// const [techStacks, setTechStacks] = useState(component.classes || []);

Expand All @@ -49,19 +39,6 @@ export function ComponentTab() {
setDescription(
component.description === undefined ? "" : component.description
);

setIsEditing((_) => {
if (readOnly) {
return false;
}
if (!component.description) {
return true;
}
if (component.description.trim() === "") {
return true;
}
return false;
});
}, [component.description]);

// useEffect(() => {
Expand All @@ -75,14 +52,6 @@ export function ComponentTab() {
// useEffect(() => {
// setSystemId(component.systemId === undefined ? "" : component.systemId);
// }, [component.systemId]);
function handleDescriptionOnBlur(newFields) {
if (description.trim() === "") {
setIsEditing(true);
} else {
setIsEditing(false);
}
updateFields(newFields);
}

function updateFields(newFields) {
dispatch(
Expand All @@ -93,6 +62,10 @@ export function ComponentTab() {
);
}

function updateDescription(description) {
updateFields({ description });
}

// // Update type
// useEffect(() => {
// if (type !== component.type) {
Expand Down Expand Up @@ -197,32 +170,12 @@ export function ComponentTab() {
}}
readOnly={readOnly}
/>
{isEditing && !readOnly && (
<TextField
fullWidth
multiline
variant="standard"
label="Description"
placeholder="Explain the purpose and function of this component"
disabled={readOnly}
value={description}
inputRef={(e) => (descriptionTextFieldRef.current = e)}
onBlur={() => handleDescriptionOnBlur({ description })}
onChange={(e) => setDescription(e.target.value)}
onKeyDown={(e) => shouldBlur(e)}
/>
)}
{(!isEditing || readOnly) && (
<>
<Typography variant="body1">Description</Typography>
<DescriptionPreview
description={description}
showDescriptionTextField={showDescriptionTextField}
readOnly={readOnly}
sx={{ fontSize: "14px" }}
/>
</>
)}
<EditableDescription
readOnly={readOnly}
description={description}
showPreviewTitle
updateDescription={updateDescription}
/>
</Box>
</CardContent>
</Card>
Expand Down
Loading

0 comments on commit a0d745e

Please sign in to comment.