Reputation: 267
When I try to make a blog, I can't pass the editor in the form. I found this:
DraftJS React-Hook-Form - submitting Editor as input
but it seems that LexicalRichTextEditor does not have such a tag to pass. Can anyone help me? How can I pass properties to achieve the Add Content and Modify Content functionality?
type LexicalEditorProps = {
//config: Parameters<typeof LexicalComposer>["0"]["initialConfig"];
content: any;
};
export default function MyEditor(props: LexicalEditorProps) {
const [ editor ] = useLexicalComposerContext();
const editorStateRef = useRef();
const [saveContent, setSaveContent] = useState('');
const editorConfig: any = {
// The editor theme
theme: EditorTheme,
// Handling of errors during update
onError(error: any) {
throw error;
},
editorState: props.content,
// Any custom nodes go here
nodes: [
HeadingNode,
ListNode,
ListItemNode,
QuoteNode,
CodeNode,
CodeHighlightNode,
TableNode,
TableCellNode,
TableRowNode,
AutoLinkNode,
LinkNode
]
};
useEffect(()=>{
if(editorStateRef.current){
setSaveContent(JSON.stringify(editorStateRef.current));
}
editor.update(()=>{
const root = $getRoot();
const selection = $getSelection();
const paragraphNode = $createParagraphNode();
const textNode = $createTextNode(saveContent);
paragraphNode.append(textNode);
root.append(paragraphNode);
});
},[saveContent]);
return (
<LexicalComposer initialConfig={editorConfig}>
<div className="editor-container">
<ToolbarPlugin />
<div className="editor-inner">
<RichTextPlugin
contentEditable={<ContentEditable className="editor-input" />}
placeholder={<Placeholder />}
ErrorBoundary={LexicalErrorBoundary}
/>
<OnChangePlugin onChange={(editorState:any) => editorStateRef.current = editorState} />
<HistoryPlugin />
<AutoFocusPlugin />
<CodeHighlightPlugin />
<ListPlugin />
<LinkPlugin />
<AutoLinkPlugin />
<ListMaxIndentLevelPlugin maxDepth={7} />
<MarkdownShortcutPlugin transformers={TRANSFORMERS} />
</div>
</div>
</LexicalComposer>
);
}
export function MyForm(){
const {register, handleSubmit, control, formState: {errors}} = useForm();
const onSubmit = ( data:any) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={2}>
<Card>
<Controller control={control} name="content" render={()=> (
<MyEditor content={dataSet.content} />
)} />
</Card>
<Box>
<Button variant="contained" type="submit">Save</Button>
</Box>
</Stack>
</form>
);
}
Upvotes: 7
Views: 5892
Reputation: 119
Costly but does the job, would be better to move this outside the onchange and have it under when focus is lost.... I will reply once I have further improved the solution.
import OnChangePlugin
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'
Make use of the plugin within the composer
<LexicalComposer initialConfig={editorConfig}>
<LoadInitialContent initialContent={jobDescription} />
<div className={styles.editorcontainer}>
<ToolbarPlugin />
<div className={styles.editorinner}>
<RichTextPlugin
contentEditable={<ContentEditable className={styles.editorinput} />}
placeholder={<Placeholder />}
ErrorBoundary={LexicalErrorBoundary}
/>
<HistoryPlugin />
// here
<OnChangePlugin onChange={onChange} />
<AutoFocusPlugin />
<CodeHighlightPlugin />
<ListPlugin />
<LinkPlugin />
<AutoLinkPlugin />
<ListMaxIndentLevelPlugin maxDepth={7} />
</div>
</div>
</LexicalComposer>
Update your editor props to expect on change method
interface EditorProps {
onChange: (
editorState: EditorState,
editor: LexicalEditor,
tags: Set<string>
) => void
jobDescription?: string | undefined
}
Use control in react hook forms
<Controller
render={({ field }) => (
<Editor
onChange={(
editorState: EditorState,
editor: LexicalEditor,
tags: Set<string>
) => {
editor.update(() => {
const htmlString = $generateHtmlFromNodes(editor, null)
field.onChange(htmlString)
})
}}
jobDescription={field.value}
/>
)}
name="description"
control={control}
defaultValue=""
rules={{ required: true, maxLength: characterLengths.description }}
/>
Upvotes: 0
Reputation: 267
I got the solution, but it's not my doing, it's the great others who helped me with the problem and hopefully will help others. https://codesandbox.io/s/purple-water-xf50bi?file=/src/App.tsx
UPDATE: Remove the if (editorRef.current ! == undefined) statement and just append field content to data
export default function App() {
const schema = yup
.object({
title: yup.string().required(),
category: yup.string().required(),
tags: yup.array().required()
})
.required();
const { register, handleSubmit } = useForm({
resolver: yupResolver(schema)
});
//Get Editor State
const editorRef: any = useRef();
const onSubmit = (data: any) => {
data.content= JSON.stringify(editorRef.current.getEditorState())
console.log(data);
};
//if (editorRef.current !== undefined) {
// if (editorRef.current !== null) {
// const latestEditorState = editorRef.current.getEditorState();
// const textContent = latestEditorState.read(() =>
// //You could change getTextContent() for your purpose
// $getRoot().getTextContent()
// );
// console.log(textContent);
// }
//}
return (
<div className="App">
<form onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={2}>
<input {...register("title")} type="text" placeholder="Title" />
<input {...register("category")} type="text" placeholder="Category" />
<input type="" placeholder="Tags" />
<select {...register("tags")} id="tags" multiple>
<option value="nginx">nginx</option>
<option value="java">java</option>
<option value="react">react</option>
<option value="mui">mui</option>
</select>
<Card elevation={3}>
<MyEditor ref={editorRef} />
</Card>
<button type="submit">Save</button>
</Stack>
</form>
</div>
);
}
MyEditor.tsx
function Placeholder() {
return <div className="editor-placeholder">Enter some rich text...</div>;
}
const editorConfig: any = {
// The editor theme
theme: EditorTheme,
// Handling of errors during update
onError(error: any) {
throw error;
},
// Any custom nodes go here
nodes: [
HeadingNode,
ListNode,
ListItemNode,
QuoteNode,
CodeNode,
CodeHighlightNode,
TableNode,
TableCellNode,
TableRowNode,
AutoLinkNode,
LinkNode
]
};
// ADDED THIS:
const EditorCapturePlugin = React.forwardRef((props: any, ref: any) => {
const [editor] = useLexicalComposerContext();
useEffect(() => {
ref.current = editor;
return () => {
ref.current = null;
};
}, [editor, ref]);
return null;
});
export const MyEditor = React.forwardRef((props: any, ref: any) => {
return (
<LexicalComposer initialConfig={editorConfig}>
<div className="editor-container">
<ToolbarPlugin />
<div className="editor-inner">
<RichTextPlugin
contentEditable={<ContentEditable className="editor-input" />}
placeholder={<Placeholder />}
ErrorBoundary={LexicalErrorBoundary}
/>
{/* ADDED THIS: */}
<EditorCapturePlugin ref={ref} />
<HistoryPlugin />
<AutoFocusPlugin />
<CodeHighlightPlugin />
<ListPlugin />
<LinkPlugin />
<AutoLinkPlugin />
<ListMaxIndentLevelPlugin maxDepth={7} />
<MarkdownShortcutPlugin transformers={TRANSFORMERS} />
</div>
</div>
</LexicalComposer>
);
});
Upvotes: 6
Reputation: 66
Not pretty but it works great, in this case I need to store both the editorState (to be able to edit the field after saving it in the db) and the content itself in html
<Controller
control={control}
name={`${fieldName}.payload.content`}
render={({ field: fieldContent }) => (
<Controller
control={control}
name={`${fieldName}.payload.editorState`}
render={({ field: fieldEditorState }) => (
<RichText
background={backgroundColor}
placeholder={t('create-campaign-form:create-text-for-campaign')}
onChange={value => {
fieldEditorState.onChange(value.editorState)
fieldContent.onChange(value.content)
}}
value={fieldEditorState.value}
/>
)}
/>
)}
/>
The properties value
and onChange
are used like so
// RichText.tsx
const RichText = ({
placeholder = '',
onChange,
value,
background,
}: EditorProps) => {
const placeholderElement = <PlaceholderElement placeholder={placeholder} />
const [floatingAnchorElem, setFloatingAnchorElem] =
useState<HTMLDivElement | null>(null)
const onRef = (_floatingAnchorElem: HTMLDivElement) => {
if (_floatingAnchorElem !== null) {
setFloatingAnchorElem(_floatingAnchorElem)
}
}
const initialConfig: InitialConfigType = {
namespace: 'RichTextEditor',
nodes: [...Nodes],
onError: (error: Error) => {
throw error
},
theme,
...(value && {
editorState: typeof value !== 'string' ? JSON.stringify(value) : value,
}),
}
return (
<LexicalComposer initialConfig={initialConfig}>
<ToolbarPlugin />
<EditorContainer background={background}>
<LexicalAutoLinkPlugin />
<RichTextPlugin
contentEditable={
<EditorScroller>
<EditorWrapper ref={onRef}>
<ContentEditable className={'ContentEditable__root'} />
</EditorWrapper>
</EditorScroller>
}
placeholder={placeholderElement}
ErrorBoundary={null}
/>
<LinkPlugin />
<ClickableLinkPlugin />
{floatingAnchorElem && (
<FloatingLinkEditorPlugin anchorElem={floatingAnchorElem} />
)}
</EditorContainer>
<OnChangePlugin onChange={onChange} />
</LexicalComposer>
)
}
// plugins/onChange.tsx
interface onChangePluginProps {
onChange: (...event: any[]) => void
}
export default function OnChangePlugin({
onChange,
}: onChangePluginProps): JSX.Element {
return (
<LexicalOnchangePlugin
onChange={(editorState: EditorState, editor: LexicalEditor) => {
editorState.read(() => {
const htmlString = $generateHtmlFromNodes(editor, null)
onChange({ content: htmlString, editorState })
})
}}
/>
)
}
Upvotes: 3