so I have 2 components.
- The TextEditor by lexical
- Insight component which uses the TextEditor and adds some functionality The TextEditor code:
import ExampleTheme from "./themes/ExampleTheme";
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin";
import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
import ToolbarPlugin from "./plugins/ToolbarPlugin";
import { HeadingNode, QuoteNode } from "@lexical/rich-text";
import { TableCellNode, TableNode, TableRowNode } from "@lexical/table";
import { ListItemNode, ListNode } from "@lexical/list";
import { CodeHighlightNode, CodeNode } from "@lexical/code";
import { AutoLinkNode, LinkNode } from "@lexical/link";
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
import { TRANSFORMERS } from "@lexical/markdown";
import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentLevelPlugin";
import CodeHighlightPlugin from "./plugins/CodeHighlightPlugin";
import AutoLinkPlugin from "./plugins/AutoLinkPlugin";
import "./TextEditor.css";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { useEffect } from "react";
function Placeholder() {
return <div className="editor-placeholder">כתוב את התובנה כאן:</div>;
}
const editorConfig = {
// The editor theme
theme: ExampleTheme,
// Handling of errors during update
onError(error) {
throw error;
},
// Any custom nodes go here
nodes: [
HeadingNode,
ListNode,
ListItemNode,
QuoteNode,
CodeNode,
CodeHighlightNode,
TableNode,
TableCellNode,
TableRowNode,
AutoLinkNode,
LinkNode,
],
};
const emptyEditor =
'{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}';
function TextEditorEditable({ editable = false }) {
const [editor] = useLexicalComposerContext();
useEffect(() => {
// defines whether the editor is on edit or read mode
editor.setEditable(editable);
}, [editor, editable]);
return null;
}
function TextEditorInitialValue({ value = null }) {
const [editor] = useLexicalComposerContext();
useEffect(() => {
// defines whether the editor is on edit or read mode
if (value === null) return;
const initialEditorState = editor.parseEditorState(
value === "" ? emptyEditor : value
);
editor.setEditorState(initialEditorState);
}, [editor, value]);
return null;
}
export default function TextEditor({ editMode, onChange, initialValue }) {
if (initialValue) {
editorConfig["editorState"] = initialValue;
}
editorConfig["editable"] = editMode;
return (
<LexicalComposer initialConfig={editorConfig}>
<div className="editor-container">
{editMode && <ToolbarPlugin />}
<div className="editor-inner">
<RichTextPlugin
contentEditable={<ContentEditable className="editor-input" />}
placeholder={<Placeholder />}
ErrorBoundary={LexicalErrorBoundary}
editorState={initialValue}
/>
<OnChangePlugin onChange={onChange} />
<HistoryPlugin />
<AutoFocusPlugin />
<CodeHighlightPlugin />
<ListPlugin />
<LinkPlugin />
<AutoLinkPlugin />
<ListMaxIndentLevelPlugin maxDepth={7} />
<MarkdownShortcutPlugin transformers={TRANSFORMERS} />
</div>
</div>
<TextEditorEditable editable={editMode} />
<TextEditorInitialValue value={initialValue} />
</LexicalComposer>
);
}
The Insight code:
import react, { useCallback, useEffect, useMemo } from "react";
import { useAssessmentContext } from "../contexts/AssessmentContext";
import TextEditor from "./TextEditor";
import { useFetchInsightsQuery } from "../store";
function assignInsights(insights, apiInsights) {
const emptyInsights = Object.keys(insights).reduce((obj, key) => {
obj[key] = "";
return obj;
}, {});
return Object.assign(emptyInsights, apiInsights);
}
function useApiData() {
const { activeAssessment, setInsights } = useAssessmentContext();
const {
data: assessmentInsights,
// error: insightsError,
// isLoading: insightsIsLoading,
} = useFetchInsightsQuery(activeAssessment?.id, {
skip: activeAssessment?.id ? false : true,
});
useEffect(() => {
if (!assessmentInsights) return;
const apiInsights = assessmentInsights.reduce(
(newInsightObject, insight) => {
newInsightObject[insight.insight_name] = insight.info;
return newInsightObject;
},
{}
);
setInsights((insights) => assignInsights(insights, apiInsights));
}, [assessmentInsights, setInsights, activeAssessment]);
}
function Insight({ insightName }) {
const { insights, setInsights, editMode, activeAssessment } =
useAssessmentContext();
useApiData();
// Get from api
const currentSavedInsightText = insights[insightName];
// The value is of type editorState
const onChange = useCallback(
(value) => {
const newValue = JSON.stringify(value);
const oldValue = insights[insightName];
if (newValue === oldValue) return;
setInsights((insights) => ({ ...insights, [insightName]: newValue }));
},
[insightName, setInsights] // eslint-disable-line react-hooks/exhaustive-deps
);
// Only re-reender the component if the insight saved value changes
const memoizedTextEditor = useMemo(() => {
return (
<TextEditor
onChange={onChange}
editMode={editMode}
initialValue={currentSavedInsightText}
/>
);
}, [editMode, onChange, activeAssessment, currentSavedInsightText]); // eslint-disable-line react-hooks/exhaustive-deps
return memoizedTextEditor;
}
export default react.memo(Insight);
The vital part to understanding the question is this:
insight is an object with multiple keys insightName: value I have this variable:
const currentSavedInsightText = insights[insightName];
Which sets the initial value for the TextEditor.
Now the problem starts with me having 2 Insight of the same kind (same insightName) One on the screen one that opens as a popup (expand on the whole screen easier to write) meaning I need to synchronize the two.
Now The only way I found possible to do this is by adding the currentSavedInsightText to the useMemo dependencies. Still, then the TextEditor loses focus and only lets me write a single letter at a time.
What can I do for the TextEditor to either not lose focus or use a state for its value as you do with a normal input where you connect the value to always show a state and change that state on keystroke