useRef undefined error using nextjs and remirror editor component - javascript

I'm using NextJS and Remirror Editor plugin. I get an error that setContent is undefined on the index page where the editor is loaded. I want to add an "external" button after the Component is loaded to exit the text. The component is dynamically externally loaded. I'm really unsure how to make the external button/text change to work.
Index.tsx:
import { NextPage, GetStaticProps } from 'next';
import dynamic from 'next/dynamic';
import { WysiwygEditor } from '#remirror/react-editors/wysiwyg';
import React, { useRef, useEffect } from 'react';
const TextEditor = dynamic( () => import('../text-editor'), { ssr: false } );
import natural from "natural";
export interface EditorRef {
setContent: (content: any) => void;
}
type Props = {
aaa:string
bbb:string
}
const Home: NextPage< Props > = (props) => {
//hook call ref to use editor externally
const ref = useRef<EditorRef | null>(null);
const {aaa,bbb} = props;
return (
<>
<ul>
{aaa} </ul>
Next.js Home Page
<TextEditor ref={ref} />
{
useEffect(()=>{
ref.current!.setContent({content: "testing the text has changed"})
},[])}
<button onClick={() => ref.current.setContent({content: "testing the text has changed"})}>Set another text button 2</button>
</>
);
};
var stringWordNet = "";
export const getStaticProps = async ():Promise<GetStaticPropsResult<Props>> => {
var wordnet = new natural.WordNet();
wordnet.lookup('node', function(results) {
stringWordNet = String(results[0].synonyms[1]);
});
return {
props:{
aaa:stringWordNet,
bbb:"bbb"
}
}
};
export default Home;
text-editor.tsx
import 'remirror/styles/all.css';
import { useEffect, useState, forwardRef, Ref, useImperativeHandle, useRef } from 'react';
import { BoldExtension,
ItalicExtension,
selectionPositioner,
UnderlineExtension, MentionAtomExtension } from 'remirror/extensions';
import { cx } from '#remirror/core';
import {
EditorComponent,
FloatingWrapper,
MentionAtomNodeAttributes,
Remirror,
useMentionAtom,
useRemirror, ThemeProvider, useRemirrorContext
} from '#remirror/react';
import { css } from "#emotion/css";
const styles = css`
background-color: white;
color: #101010;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px;
`;
const ALL_USERS = [
{ id: 'wordnetSuggestion1', label: 'NotreSuggestion1' },
{ id: 'wordnetSuggestion2', label: 'NotreSuggestion2' },
{ id: 'wordnetSuggestion3', label: 'NotreSuggestion3' },
{ id: 'wordnetSuggestion4', label: 'NotreSuggestion4' },
{ id: 'wordnetSuggestion5', label: 'NotreSuggestion5' },
];
const MentionSuggestor: React.FC = () => {
return (
<FloatingWrapper positioner={selectionPositioner} placement='bottom-start'>
<div>
{
ALL_USERS.map((option, index) => (
<li> {option.label}</li>
))
}
</div>
</FloatingWrapper>
);
};
const DOC = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'New content',
},
],
},
],
};
//Start Imperative Handle Here
export interface EditorRef {
setContent: (content: any) => void;
}
const ImperativeHandle = forwardRef((_: unknown, ref: Ref<EditorRef>) => {
const { setContent } = useRemirrorContext({
autoUpdate: true,
});
// Expose content handling to outside
useImperativeHandle(ref, () => ({ setContent }));
return <></>;
});
//Make content to show.
const TextEditor = forwardRef((_: unknown, ref: Ref<EditorRef>) => {
const editorRef = useRef<EditorRef | null>(null);
const { manager, state, getContext } = useRemirror({
extensions: () => [
new MentionAtomExtension()
],
content: '<p>I love Remirror</p>',
selection: 'start',
stringHandler: 'html',
});
return (
<>
<button
onMouseDown={(event) => event.preventDefault()}
onClick={() => editorRef.current!.setContent(DOC)}
></button>
<div className='remirror-theme'>
<Remirror manager={manager} initialContent={state}>
<EditorComponent />
<MentionSuggestor />
<ImperativeHandle ref={editorRef} />
</Remirror>
</div>
</>
);
});
export default TextEditor;

Related

tinymce react plugin registration does not work

Was trying to register a custom plugin in tinymce but ever attempt failed.
Created a plugin using yoman generator build it and reference in the project
Created a plugin using yoman generator directly reference in the project without building
Also tried the below approach is still doesn't work
import { FC, RefObject, useLayoutEffect, useRef } from 'react'
import { Editor } from '#tinymce/tinymce-react';
import { plugins, toolbar, quickBarsInsertToolbar } from './config';
import tinymce, { Editor as TinyMCEEditor, PluginManager } from 'tinymce';
import './index.css'
// import '../../plugins/dropdown-plugin/dist/dropdown-plugin/plugin.min.js';
const TinymceEditor: FC = () => {
const editorRef = useRef<TinyMCEEditor | null>(null);
const log = () => {
if (editorRef.current) {
console.log(editorRef.current.getContent());
}
};
useLayoutEffect(() => {
tinymce.PluginManager.add("dropdown-plugin", function (n, t) { n.ui.registry.addButton("dropdown-plugin", { text: "dropdown-plugin button", onAction: function () { n.setContent("<p>content added from dropdown-plugin</p>") } }) })
}, [])
return (
<div
className='tinymce_editor'
>
<Editor
onInit={(evt, editor) => editorRef.current = editor}
init={{
height: 500,
plugins:['dropdown-plugin'],
toolbar:'dropdown-plugin'
}}
/>
<button onClick={log}>Log editor content</button>
</div>
)
}
export default TinymceEditor;
Gives this error
Finally Working now
import { FC, useLayoutEffect, useRef } from 'react'
import { Editor } from '#tinymce/tinymce-react';
import { plugins, toolbar, quickBarsInsertToolbar } from './config';
import { Editor as TinyMCEEditor } from 'tinymce';
import './index.css'
import { registerPlugins } from './plugin/register-plugins';
const TinymceEditor: FC = () => {
const editorRef = useRef<TinyMCEEditor | null>(null);
const log = () => {
if (editorRef.current) {
console.log(editorRef.current.getContent());
}
};
useLayoutEffect(() => {
registerPlugins()
}, [])
return (
<div
className='tinymce_editor'
>
<Editor
onInit={(evt, editor) => editorRef.current = editor}
init={{
height: 500,
plugins: plugins,
quickbars_insert_toolbar: quickBarsInsertToolbar,
toolbar: toolbar
}}
/>
<button onClick={log}>Log editor content</button>
</div>
)
}
export default TinymceEditor;
import { customQuickBarButton } from './customQuickBarButton';
import tinymce from 'tinymce';
import { customToolBarButtonMenu } from './customToolBarButtonMenu';
export const registerPlugins = () => {
tinymce.PluginManager.add(
'custom-quickbar-button-plugin',
customQuickBarButton
);
tinymce.PluginManager.add(
'custom-toolbar-button-menu-plugin',
customToolBarButtonMenu
);
};
import ReactDOM from 'react-dom';
import { Editor } from 'tinymce';
export const customToolBarButtonMenu = (editor: Editor) => {
editor.ui.registry.addMenuButton('custom-toolbar-button-menu-plugin', {
text: 'Custom Menu',
icon: 'language',
fetch: callback => {
var items = [
{
type: 'menuitem',
text: 'Menu Item 1',
icon: 'arrow-right',
onAction: () => {
editor.insertContent(' <em>You clicked menu item 1!</em> ');
},
},
{
type: 'menuitem',
text: 'Menu Item 2',
icon: 'user',
onAction: () => {
const div = document.createElement('div');
ReactDOM.render(
<div>
<span>Signature with image</span>
<img src="https://i.picsum.photos/id/532/200/200.jpg?hmac=PPwpqfjXOagQmhd_K7H4NXyA4B6svToDi1IbkDW2Eos" />
</div>,
div
);
editor.selection.setNode(div);
},
},
];
callback(items as any);
},
});
};

User preferred language saved but the page content language didn't change only after I reload

I'm using ant design pro.
The idea is In the platform we have 2 languages to choose from Fr(French) and En(English),
I want the user when he logs in and change the language to English for example when he logs out and log in again the language should be saved to English so he would be able to see the content in English, I managed to do it, when i login in the backend the preferedLanguage = en, the language toggle in the also changes to en, the only problem the web page content stays in French it's only change in English when i reload the page.
I think the issue is related to the login page, the login page is set to French as default , let's say my preferred language now is English, if i login from the login page. The page content loaded in French only changes when i reload.
-This is the umijs documentation : https://umijs.org/docs/max/i18n#setlocale-%E8%AE%BE%E7%BD%AE%E8%AF%AD%E8%A8%80
-LanguageDropdown (the toggle where you select the language (Fr or En)
import type { FC } from 'react';
import React, { useState, useEffect } from 'react';
import { Menu, Dropdown } from 'antd';
import { CaretDownOutlined, CaretUpOutlined, GlobalOutlined } from '#ant-design/icons';
import styles from './index.less';
import { setLocale, getLocale, getAllLocales } from 'umi';
interface Props {
setUpdateLang: any;
currentLang: string;
}
const LanguageDropdown: FC<Props> = ({ currentLang, setUpdateLang }) => {
const [langvisible, setLangVisible] = useState<boolean>(false);
const [localesList, setLocalesList] = useState<any[]>([]);
const [currentLocale, setCurrentLocale] = useState<string>('');
// useEffect(() => {
// if (currentLang) {
// setLocalesList(getAllLocales());
// setCurrentLocale(getLocale());
// setLocale(currentLang === 'fr' ? 'fr-FR' : 'en-US');
// }
// alert(currentLang);
// alert(getLocale());
// }, [currentLang]);
useEffect(() => {
setLocalesList(getAllLocales());
setCurrentLocale(getLocale());
if (currentLang) {
// alert(222);
const selectedLang = currentLang === 'fr' ? 'fr-FR' : 'en-US';
// setNewLang(selectedLang);
setLocale(selectedLang, false);
setCurrentLocale(getLocale());
}
}, [currentLang]);
const onLangVisibleChange = (visibleLang: boolean) => {
setLangVisible(visibleLang);
};
const langNameHandler = (lang: string) => {
if (lang === 'en-US') return 'EN';
else if (lang === 'fr-FR') return 'FR';
return 'FR';
};
const setNewLang = (lang: string) => {
setUpdateLang({ lang: langNameHandler(lang).toLocaleLowerCase(), updated: true });
setLocale(lang);
};
const Langmenu = (
<Menu>
{localesList?.map((lang: any) => (
<Menu.Item key={lang}>
<a onClick={() => setNewLang(lang)}>{langNameHandler(lang)}</a>
</Menu.Item>
))}
</Menu>
);
return (
<div className={styles.profileDropdownContainer}>
<Dropdown
overlay={Langmenu}
placement="bottomLeft"
trigger={['click']}
onVisibleChange={onLangVisibleChange}
className={styles.dropdown}
>
<div className={styles.langContainer}>
<span>
<GlobalOutlined /> {langNameHandler(currentLocale)}
</span>
{!langvisible ? <CaretDownOutlined /> : <CaretUpOutlined />}
</div>
</Dropdown>
</div>
);
};
export default LanguageDropdown;
-RightContext
import { Tag } from 'antd';
import type { Settings as ProSettings } from '#ant-design/pro-layout';
import React, { useEffect, useState } from 'react';
import type { ConnectProps } from 'umi';
import type { Dispatch } from 'umi';
import { connect } from 'umi';
import type { ConnectState } from '#/models/connect';
import Avatar from './AvatarDropdown';
import styles from './index.less';
import LanguageDropdown from '../languageDropdown';
import moment from 'moment';
export type GlobalHeaderRightProps = {
dispatch: Dispatch;
theme?: ProSettings['navTheme'] | 'realDark';
auth: any;
users: any;
platformLanguage: any;
data: any;
} & Partial<ConnectProps> &
Partial<ProSettings>;
const ENVTagColor = {
dev: 'orange',
test: 'green',
pre: '#87d068',
};
const GlobalHeaderRight: React.FC<GlobalHeaderRightProps> = (props) => {
const [updateLang, setUpdateLang] = useState<{ lang: string; updated: boolean }>({
lang: '',
updated: false,
});
const [currentLang, setCurrentLang] = useState<any>(null);
const { theme, layout, auth, platformLanguage, data, dispatch } = props;
let className = styles.right;
useEffect(() => setCurrentLang(platformLanguage), [platformLanguage]);
useEffect(() => {
if (updateLang.updated) {
const {
organization,
roles,
email,
deleted,
department,
createdById,
organizationId,
...rest
} = data;
const birthdate = moment(rest.birthdate).format('YYYY-MM-DD');
const workversary = moment(rest.workversary).format('YYYY-MM-DD');
dispatch({
type: 'users/updateMe',
payload: {
data: { ...rest, birthdate, workversary, platformLanguage: updateLang.lang },
userId: auth?.currentUser.id,
},
});
setUpdateLang({ ...updateLang, updated: false });
setCurrentLang(updateLang.lang);
}
}, [updateLang]);
if (theme === 'dark' && layout === 'top') {
className = `${styles.right} ${styles.dark}`;
}
return (
<div className={className}>
{currentLang ? (
<LanguageDropdown currentLang={currentLang} setUpdateLang={setUpdateLang} />
) : null}
<Avatar />
{REACT_APP_ENV && (
<span>
<Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag>
</span>
)}
</div>
);
};
export default connect(({ settings, auth, users }: ConnectState) => ({
theme: settings.navTheme,
layout: settings.layout,
auth,
users,
platformLanguage: auth?.currentUser?.membership?.platformLanguage,
data: auth?.currentUser?.membership,
}))(GlobalHeaderRight);
-LoginLayout
import React, { useEffect, useState } from 'react';
import type { ConnectState } from '#/models/connect';
import type { MenuDataItem } from '#ant-design/pro-layout';
import { getMenuData, getPageTitle } from '#ant-design/pro-layout';
import { useIntl, connect } from 'umi';
import type { ConnectProps } from 'umi';
import { Col, Row } from 'antd';
import { Helmet, HelmetProvider } from 'react-helmet-async';
import LoginImage from '../assets/loginImage.png';
import SignUpAdminImage from '../assets/adminSignup.svg';
import styles from './LoginLayout.less';
import LanguageDropdown from '#/components/languageDropdown';
import SignupSideText from '#/components/SignupSideText';
export type UserLayoutProps = {
breadcrumbNameMap: Record<string, MenuDataItem>;
} & Partial<ConnectProps>;
const LoginLayout: React.FC<UserLayoutProps> = (props) => {
const [layoutImage, setLayoutImage] = useState(LoginImage);
const [updateLang, setUpdateLang] = useState<{ lang: string; updated: boolean }>({
lang: '',
updated: false,
});
const [currentLang, setCurrentLang] = useState<any>('');
useEffect(() => {
if (updateLang.updated) {
setUpdateLang({ ...updateLang, updated: false });
setCurrentLang(updateLang.lang);
}
}, [updateLang]);
useEffect(() => {
if (window.location.pathname === '/user/adminSignup/step1') {
setLayoutImage(SignUpAdminImage);
} else if (window.location.pathname === '/user/adminSignup/step2') {
setLayoutImage('TextSignup');
} else setLayoutImage(LoginImage);
}, [window.location.pathname]);
const {
route = {
routes: [],
},
} = props;
const { routes = [] } = route;
const {
children,
location = {
pathname: '',
},
} = props;
const { formatMessage } = useIntl();
const { breadcrumb } = getMenuData(routes);
const title = getPageTitle({
pathname: location.pathname,
formatMessage,
breadcrumb,
...props,
});
return (
<>
<HelmetProvider>
<Helmet>
<title>{title}</title>
<meta name="description" content={title} />
</Helmet>
<div className={styles.container}>
<Col
xl={{ span: 12, order: 1 }}
xs={{ span: 0, order: 2 }}
md={{ span: 0, order: 2 }}
lg={{ span: 0, order: 2 }}
style={{ backgroundColor: '#00bfa5' }}
>
{layoutImage === 'TextSignup' ? (
<SignupSideText />
) : (
<img alt="logo" width="100%" height="100%" src={layoutImage} />
)}
</Col>
<Col
xl={{ span: 12, order: 2 }}
lg={{ span: 24, order: 2 }}
sm={{ span: 24, order: 1 }}
xs={{ span: 24, order: 1 }}
>
{' '}
<Row justify="end" className="languageRow">
<LanguageDropdown currentLang={currentLang} setUpdateLang={setUpdateLang} />
</Row>
{children}
</Col>
</div>
</HelmetProvider>
</>
);
};
export default connect(({ settings }: ConnectState) => ({ ...settings }))(LoginLayout);

React-Redux. After changing state by useDispatch, components not updating by useSelector

I have useSelectors to check if my App component is updating. The first at the beginning of App component and the second one at App return part. By clicking on letters, currendElementId in store is updating with no problem (I checked by Redux DevTools). But useSelectors not updating.
I know that state should update in reducers immutable, but in createSlice we may use mutable code, so that's not my case. (Anyway, I have already tried to make state copy, change it and then send it, but it works same way)
There are my code:
store.js
import { configureStore } from "#reduxjs/toolkit";
import pagesReducer from "./../features/Pages/pagesSlice.js";
export default configureStore({
reducer: {
pagesInfo: pagesReducer,
},
})
pageSlice.js
import { createSlice, nanoid } from "#reduxjs/toolkit";
const initialState = {
currentPage: 0,
currentElementId: null,
pages: [ // Pages
{ // Page
id: nanoid(),
lines: [
{ // Line
id: nanoid(),
aligned: "left",
content: [
{ // Symbol
id: nanoid(),
type: "symbol",
symbol: "F",
isBold: false,
isItalic: false,
isUnderlined: false,
fontSize: 18,
color: "black",
},
{ // Symbol
id: nanoid(),
type: "symbol",
symbol: "o",
isBold: false,
isItalic: false,
isUnderlined: false,
fontSize: 18,
color: "black",
},
{ // Symbol
id: nanoid(),
type: "symbol",
symbol: "r",
isBold: false,
isItalic: false,
isUnderlined: false,
fontSize: 18,
color: "black",
},
],
},
{
id: nanoid(),
aligned: "left",
content: [
{ // Image
id: nanoid(),
type: "image",
imageSrc: "./img/me and the boys.png",
width: 200,
height: 200,
},
],
},
],
},
],
}
const textSlice = createSlice({
name: "pagesInfo",
initialState,
reducers: {
changeCurrentElementID(state, action) {
state.currentElementId = action.payload;
// return {
// ...state,
// currentElementId: action.payload,
// }
}
},
})
export const { changeCurrentElementID } = textSlice.actions;
export default textSlice.reducer;
index.js
import React, { useState } from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import store from "./app/store.js";
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
App.js
import css from "./App.module.css";
// import BoldingTextButton from "./features/BoldingTextButton/BoldingTextButton.js";
import "./nullStyle.css";
import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { changeCurrentElementID } from "./features/Pages/pagesSlice.js";
import EnterImageButton from "./features/Pages/EnterImage/EnterImageButton.js";
import TextSizing from "./features/Pages/TextSizing/TextSizing.js";
import TextDecoration from "./features/Pages/TextDecoration/TextDecoration.js";
import TextAligning from "./features/Pages/TextAligning/TextAligning.js";
function App(props) {
console.log(useSelector(state => state.currentElementId));
const dispatch = useDispatch();
// document.addEventListener("click", event => {
// const target = event.target.closest("li")
// if (target) {
// const elementID = target.getAttribute("elementid");
// dispatch(changeCurrentElementID(elementID));
// }
// })
const changeElementID = event => {
const target = event.target.closest("li")
if (target) {
const elementID = target.getAttribute("elementid");
dispatch(changeCurrentElementID(elementID));
}
}
const GetPageContent = () => {
const rawPageLinesContent = useSelector(state => state.pagesInfo.pages[state.pagesInfo.currentPage].lines);
const currentElementID = useSelector(state => state.currentElementId);
const completedPageContent = rawPageLinesContent.map(line => {
return (
<ul key={line.id} className={css["page-line"]}>
{
line.content.map(element => {
let finalElement;
if (element.type == "symbol") {
const style = { fontSize: element.fontSize + "px" }
finalElement = (
<li onClick={changeElementID} key={element.id} elementid={element.id} className={`${css["page-line__item"]} ${css["page-line__symbol"]}`} style={style}>{element.symbol}</li>
)
if (currentElementID == element.id) {
finalElement = (
<input key={element.id} elementid={element.id} maxLength="1" className={`${css["page-line__item"]} ${css["page-line__symbol"]} ${css["page-line__choosen"]}`} style={style} value={element.symbol} />
)
}
}
else if (element.type == "image") {
finalElement = (
<li onClick={changeElementID} key={element.id} elementid={element.id} className={`${css["page-line__item"]} ${css["page-line__image"]}`}>
<img src={element.imageSrc} width={element.width} height={element.height} alt="image" />
</li>
)
}
// else if (element.type == "enter") {
// finalElement = (
// <li key={element.id} elementid={element.id} className={`${css["page-line__item"]} ${css["page-line__image"]}`}>
// <br />
// </li>
// )
// }
return finalElement;
})
}
</ul >
)
})
return completedPageContent;
}
return (
<div className={css["App"]}>
<header className={`${css['App__header']} ${css['tools']} `}>
<EnterImageButton />
<TextSizing />
<TextDecoration />
<TextAligning />
<div>{useSelector(state => state.currentElementId) || 0}</div>
</header >
<div className={`${css['App__content']} ${css['pages']} `}>
<div className={`${css['pages__item']}`} >
{GetPageContent()}
</div>
</div>
</div >
);
}
export default App;
Your selector is wrong.
Since you have
export default configureStore({
reducer: {
pagesInfo: pagesReducer,
},
})
your pages slice is mounted as slice.pagesInfo.
So you need to do
useSelector(state => state.pagesInfo.currentElementId)

window is not defined - react-draft-wysiwyg used with next js (ssr)

I am working on a rich text editor used for converting plain html to editor content with next js for ssr. I got this error window is not defined so I search a solution to this github link
It used a dynamic import feature of next js.
Instead of importing the Editor directly import { Editor } from "react-draft-wysiwyg";
It uses this code to dynamically import the editor
const Editor = dynamic(
() => {
return import("react-draft-wysiwyg").then(mod => mod.Editor);
},
{ ssr: false }
);
But still I'm getting this error though I already installed this react-draft-wysiwyg module
ModuleParseError: Module parse failed: Unexpected token (19:9)
You may need an appropriate loader to handle this file type.
| import dynamic from "next/dynamic";
| var Editor = dynamic(function () {
> return import("react-draft-wysiwyg").then(function (mod) {
| return mod.Editor;
| });
And this is my whole code
import React, { Component } from "react";
import { EditorState } from "draft-js";
// import { Editor } from "react-draft-wysiwyg";
import dynamic from "next/dynamic";
const Editor = dynamic(
() => {
return import("react-draft-wysiwyg").then(mod => mod.Editor);
},
{ ssr: false }
);
class MyEditor extends Component {
constructor(props) {
super(props);
this.state = { editorState: EditorState.createEmpty() };
}
onEditorStateChange = editorState => {
this.setState({ editorState });
};
render() {
const { editorState } = this.state;
return (
<div>
<Editor
editorState={editorState}
wrapperClassName="rich-editor demo-wrapper"
editorClassName="demo-editor"
onEditorStateChange={this.onEditorStateChange}
placeholder="The message goes here..."
/>
</div>
);
}
}
export default MyEditor;
Please help me guys. Thanks.
Here is a workaround
import dynamic from 'next/dynamic'
import { EditorProps } from 'react-draft-wysiwyg'
// install #types/draft-js #types/react-draft-wysiwyg and #types/draft-js #types/react-draft-wysiwyg for types
const Editor = dynamic<EditorProps>(
() => import('react-draft-wysiwyg').then((mod) => mod.Editor),
{ ssr: false }
)
The dynamic one worked for me
import dynamic from 'next/dynamic';
const Editor = dynamic(
() => import('react-draft-wysiwyg').then((mod) => mod.Editor),
{ ssr: false }
)
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
const TextEditor = () => {
return (
<>
<div className="container my-5">
<Editor
toolbarClassName="toolbarClassName"
wrapperClassName="wrapperClassName"
editorClassName="editorClassName"
/>
</div>
</>
)
}
export default TextEditor
Draft.js WYSWYG with Next.js and Strapi Backend, Create and View Article with Image Upload
import React, { Component } from 'react'
import { EditorState, convertToRaw } from 'draft-js';
import draftToHtml from 'draftjs-to-html';
import dynamic from 'next/dynamic';
const Editor = dynamic(
() => import('react-draft-wysiwyg').then(mod => mod.Editor),
{ ssr: false })
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
export default class ArticleEditor extends Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty()
};
}
onEditorStateChange = (editorState) => {
this.setState({
editorState,
});
this.props.handleContent(
convertToRaw(editorState.getCurrentContent()
));
};
uploadImageCallBack = async (file) => {
const imgData = await apiClient.uploadInlineImageForArticle(file);
return Promise.resolve({ data: {
link: `${process.env.NEXT_PUBLIC_API_URL}${imgData[0].formats.small.url}`
}});
}
render() {
const { editorState } = this.state;
return (
<>
<Editor
editorState={editorState}
toolbarClassName="toolbar-class"
wrapperClassName="wrapper-class"
editorClassName="editor-class"
onEditorStateChange={this.onEditorStateChange}
toolbar={{
options: ['inline', 'blockType', 'fontSize', 'fontFamily', 'list', 'textAlign', 'colorPicker', 'link', 'embedded', 'emoji', 'image', 'history'],
inline: { inDropdown: true },
list: { inDropdown: true },
textAlign: { inDropdown: true },
link: { inDropdown: true },
history: { inDropdown: true },
image: {
urlEnabled: true,
uploadEnabled: true,
uploadCallback: this.uploadImageCallBack,
previewImage: true,
alt: { present: false, mandatory: false }
},
}}
/>
<textarea
disabled
value={draftToHtml(convertToRaw(editorState.getCurrentContent()))}
/>
</>
)
}
}
Try to return the Editor after React updates the DOM using useEffect hook. For instance:
const [editor, setEditor] = useState<boolean>(false)
useEffect(() => {
setEditor(true)
})
return (
<>
{editor ? (
<Editor
editorState={editorState}
wrapperClassName="rich-editor demo-wrapper"
editorClassName="demo-editor"
onEditorStateChange={this.onEditorStateChange}
placeholder="The message goes here..."
/>
) : null}
</>
)

Updating state based on different button clicks

I am trying to make a survey tool with firebase and react and I am implementing a template feature, so now one of the templates is working, when user clicks on create new survey, I have various buttons shown to them like: blank, temp1,temp2,temp3,temp4 based on whatever is clicked a clickhandler is triggered, now how do I update the content of the state based on which template i am getting? below is my code:
<button onClick={props.clicked} className={classes.Board}>
Board Survey
</button>
<button onClick={props.chair} className={classes.Chair}>
Chairperson Survey
</button>
<button onClick={props.member} className={classes.Member}>
Board Member Survey
</button>
And now i use
clicked={this.clickHandler}
The code to fetch survey template:
const fetchSurvey = async () => {
const res = await axios.get(
"/surveys/ckb0kmt3n000e3g5rmjnfw4go/content/questions.json"
);
return res.data;
};
Where "ckb0kmt3n000e3g5rmjnfw4go" is the survey id
const content = {
title: "Your Survey Tite",
subTitle: "Your Survey Description",
creatorDate: new Date(),
lastModified: new Date(),
questions: fetchSurvey().then(questions => {
this.setState(state => ({
...state,
content: {
...state.content,
questions
}
}));
}),
submitting: true
};
this.setState({
_id: newId(),
content: content,
userId: this.props.user_Id
});
}
Now how to do i update the questions key to run different functions based on which templates i choose on the previous page?
ClickHandler Code:
clickHandler = () => {
const newSurvey = { ...this.state };
axios
.put(
"/surveys/" + this.state._id + ".json?auth=" + this.props.token,
newSurvey
)
.then(res => {
this.props.onGetSurveyId(res.data._id);
})
.catch(error => {
console.log(error);
});
};
File1:
import React from "react";
import Button from "#material-ui/core/Button";
import Icon from "#material-ui/core/Icon";
import { makeStyles } from "#material-ui/core/styles";
import classes from "./NewSurvey.module.css";
import { Link } from "react-router-dom";
import Dialog from "#material-ui/core/Dialog";
import DialogActions from "#material-ui/core/DialogActions";
import DialogContent from "#material-ui/core/DialogContent";
import DialogTitle from "#material-ui/core/DialogTitle";
import InputLabel from "#material-ui/core/InputLabel";
import Input from "#material-ui/core/Input";
import MenuItem from "#material-ui/core/MenuItem";
import FormControl from "#material-ui/core/FormControl";
import Select from "#material-ui/core/Select";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
const NewSurvey = props => {
const [open, setOpen] = React.useState(false);
const [template, setTemplate] = React.useState("");
const handleChange = event => {
setTemplate(Number(event.target.value) || "");
};
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const notify = () =>
toast.info("Creating Survey....Please Wait", {
position: "top-right",
autoClose: 5000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined
});
return (
<div className={classes.CreateSurvey}>
<ToastContainer
position="top-right"
autoClose={5000}
hideProgressBar
newestOnTop
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
/>
<Dialog
disableBackdropClick
disableEscapeKeyDown
open={open}
onClose={handleClose}
className={classes.Dialog}
>
<DialogTitle className={classes.DialogTitle}>
Create New Survey
</DialogTitle>
<h5 className={classes.Info}>
Choose a template or start with a blank survey
</h5>
<DialogContent>
<button
onClick={function(event) {
props.clicked();
notify();
}}
className={classes.Blank}
>
Blank Survey
</button>
<button onClick={props.board} className={classes.Board}>
Board Survey
</button>
<button onClick={props.chair} className={classes.Chair}>
Chairperson Survey
</button>
<button onClick={props.member} className={classes.Member}>
Board Member Survey
</button>
</DialogContent>
<DialogActions>
<button onClick={handleClose} className={classes.CancelButton}>
Cancel
</button>
</DialogActions>
</Dialog>
<button
className={classes.NewSurvey}
onClick={handleClickOpen}
disabled={props.isLoading || props.error}
>
<Icon className={classes.Icon}>add_circle_outline</Icon> Create New
Survey
</button>
</div>
);
};
export default NewSurvey;
File 2:
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import newId from "../../../ulitity/idGenerator";
import axios from "../../../axios-order";
import * as actionTypes from "../../../Store/actions/actionsTypes";
import NewSurveyView from "../../../Components/NewSurvey/NewSurvey";
import { InitQuestions } from "../../../ulitity/constants/Questions";
class NewSurvey extends Component {
state = {
_id: "",
userId: "",
content: {
title: "Your Survey Title",
subTitle: "",
creatorDate: "",
lastModified: ""
}
};
componentDidMount() {
var sampleQuestion;
var questionObj;
sampleQuestion = InitQuestions["SINGLE_LINE_TEXT"]();
questionObj = {
[sampleQuestion._id]: sampleQuestion
};
var orderQuestions;
let questions = [sampleQuestion._id];
orderQuestions = questions.map(questionId => questionObj[questionId]);
const fetchBoardMember = async () => {
const res = await axios.get(
"/surveys/ckb0kmt3n000e3g5rmjnfw4go/content/questions.json"
);
return res.data;
};
const fetchBoard = async () => {
const res = await axios.get(
"/surveys/ckb24goc800013g5r7j8igrk3/content/questions.json"
);
return res.data;
};
const fetchChair = async () => {
const res = await axios.get(
"/surveys/ckb24gt3s00053g5rrf60353r/content/questions.json"
);
return res.data;
};
const content = {
title: "Your Survey Title",
subTitle: "Your Survey Description",
creatorDate: new Date(),
lastModified: new Date(),
questions: fetchBoard().then(questions => {
this.setState(state => ({
...state,
content: {
...state.content,
questions
}
}));
}),
submitting: true
};
this.setState({
_id: newId(),
content: content,
userId: this.props.user_Id
});
}
clickHandler = () => {
const newSurvey = { ...this.state };
axios
.put(
"/surveys/" + this.state._id + ".json?auth=" + this.props.token,
newSurvey
)
.then(res => {
this.props.onGetSurveyId(res.data._id);
})
.catch(error => {
console.log(error);
});
};
render() {
return (
<div>
<NewSurveyView
clicked={this.clickHandler}
board={this.boardHandler}
chair={this.chairHandler}
member={this.memberHandler}
isLoading={this.props.loading}
error={this.props.isError}
/>
</div>
);
}
}
const mapStateToProps = state => {
return {
user_Id: state.auth.userId,
token: state.auth.token,
surveyId: state.surveyEditer.survey.id
};
};
const mapDispatchToProps = dispatch => {
return {
onGetSurveyId: surveyId =>
dispatch({ type: actionTypes.GET_SURVEY_ID, surveyId: surveyId })
};
};
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(NewSurvey)
);

Categories

Resources