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}
</>
)
Related
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);
},
});
};
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;
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);
I have an application with a table, the table has a checkbox to set a Tenant as Active, this variable is a global variable that affects what the user does in other screens of the application.
On the top right of the application, I have another component called ActiveTenant, which basically shows in which tenant the user is working at the moment.
The code of the table component is like this:
import React, { Component } from 'react';
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
class ListTenants extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
fetchData = () => {
adalApiFetch(fetch, "/Tenant", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
key: row.id,
TestSiteCollectionUrl: row.TestSiteCollectionUrl,
TenantName: row.TenantName,
Email: row.Email
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render() {
const columns = [
{
title: 'TenantName',
dataIndex: 'TenantName',
key: 'TenantName',
},
{
title: 'TestSiteCollectionUrl',
dataIndex: 'TestSiteCollectionUrl',
key: 'TestSiteCollectionUrl',
},
{
title: 'Email',
dataIndex: 'Email',
key: 'Email',
}
];
// rowSelection object indicates the need for row selection
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
if(selectedRows[0].TenantName != undefined){
console.log(selectedRows[0].TenantName);
const options = {
method: 'post'
};
adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+selectedRows[0].TenantName.toString(), options)
.then(response =>{
if(response.status === 200){
Notification(
'success',
'Tenant set to active',
''
);
}else{
throw "error";
}
})
.catch(error => {
Notification(
'error',
'Tenant not activated',
error
);
console.error(error);
});
}
},
getCheckboxProps: record => ({
type: Radio
}),
};
return (
<Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
);
}
}
export default ListTenants;
And the code of the ActiveTenant component its also very simple
import React, { Component } from 'react';
import authAction from '../../redux/auth/actions';
import { adalApiFetch } from '../../adalConfig';
class ActiveTenant extends Component {
constructor(props) {
super(props);
this.state = {
tenant: ''
};
}
fetchData = () => {
adalApiFetch(fetch, "/Tenant/GetActiveTenant", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
this.setState({ tenant: responseJson.TenantName });
}
})
.catch(error => {
this.setState({ tenant: '' });
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render() {
return (
<div>You are using tenant: {this.state.tenant }</div>
);
}
}
export default ActiveTenant;
The problem is, if I have multiple tenants on my database registered and I set them to active, the server side action occurs, and the state is changed, however on the top right its still showing the old tenant as active, UNTIL I press F5 to refresh the browser.
How can I achieve this?
For the sake of complete understandment of my code I will need to paste below other components:
TopBar which contains the active tenant
import React, { Component } from "react";
import { connect } from "react-redux";import { Layout } from "antd";
import appActions from "../../redux/app/actions";
import TopbarUser from "./topbarUser";
import TopbarWrapper from "./topbar.style";
import ActiveTenant from "./activetenant";
import TopbarNotification from './topbarNotification';
const { Header } = Layout;
const { toggleCollapsed } = appActions;
class Topbar extends Component {
render() {
const { toggleCollapsed, url, customizedTheme, locale } = this.props;
const collapsed = this.props.collapsed && !this.props.openDrawer;
const styling = {
background: customizedTheme.backgroundColor,
position: 'fixed',
width: '100%',
height: 70
};
return (
<TopbarWrapper>
<Header
style={styling}
className={
collapsed ? "isomorphicTopbar collapsed" : "isomorphicTopbar"
}
>
<div className="isoLeft">
<button
className={
collapsed ? "triggerBtn menuCollapsed" : "triggerBtn menuOpen"
}
style={{ color: customizedTheme.textColor }}
onClick={toggleCollapsed}
/>
</div>
<ul className="isoRight">
<li
onClick={() => this.setState({ selectedItem: 'notification' })}
className="isoNotify"
>
<TopbarNotification locale={locale} />
</li>
<li>
<ActiveTenant />
</li>
<li
onClick={() => this.setState({ selectedItem: "user" })}
className="isoUser"
>
<TopbarUser />
<div>{ process.env.uiversion}</div>
</li>
</ul>
</Header>
</TopbarWrapper>
);
}
}
export default connect(
state => ({
...state.App.toJS(),
locale: state.LanguageSwitcher.toJS().language.locale,
customizedTheme: state.ThemeSwitcher.toJS().topbarTheme
}),
{ toggleCollapsed }
)(Topbar);
App.js which contains the top bar
import React, { Component } from "react";
import { connect } from "react-redux";
import { Layout, LocaleProvider } from "antd";
import { IntlProvider } from "react-intl";
import { Debounce } from "react-throttle";
import WindowResizeListener from "react-window-size-listener";
import { ThemeProvider } from "styled-components";
import authAction from "../../redux/auth/actions";
import appActions from "../../redux/app/actions";
import Sidebar from "../Sidebar/Sidebar";
import Topbar from "../Topbar/Topbar";
import AppRouter from "./AppRouter";
import { siteConfig } from "../../settings";
import themes from "../../settings/themes";
import { themeConfig } from "../../settings";
import AppHolder from "./commonStyle";
import "./global.css";
import { AppLocale } from "../../dashApp";
import ThemeSwitcher from "../../containers/ThemeSwitcher";
const { Content, Footer } = Layout;
const { logout } = authAction;
const { toggleAll } = appActions;
export class App extends Component {
render() {
const { url } = this.props.match;
const { locale, selectedTheme, height } = this.props;
const currentAppLocale = AppLocale[locale];
return (
<LocaleProvider locale={currentAppLocale.antd}>
<IntlProvider
locale={currentAppLocale.locale}
messages={currentAppLocale.messages}
>
<ThemeProvider theme={themes[themeConfig.theme]}>
<AppHolder>
<Layout style={{ height: "100vh" }}>
<Debounce time="1000" handler="onResize">
<WindowResizeListener
onResize={windowSize =>
this.props.toggleAll(
windowSize.windowWidth,
windowSize.windowHeight
)
}
/>
</Debounce>
<Topbar url={url} />
<Layout style={{ flexDirection: "row", overflowX: "hidden" }}>
<Sidebar url={url} />
<Layout
className="isoContentMainLayout"
style={{
height: height
}}
>
<Content
className="isomorphicContent"
style={{
padding: "70px 0 0",
flexShrink: "0",
background: "#f1f3f6",
position: "relative"
}}
>
<AppRouter url={url} />
</Content>
<Footer
style={{
background: "#ffffff",
textAlign: "center",
borderTop: "1px solid #ededed"
}}
>
{siteConfig.footerText}
</Footer>
</Layout>
</Layout>
<ThemeSwitcher />
</Layout>
</AppHolder>
</ThemeProvider>
</IntlProvider>
</LocaleProvider>
);
}
}
export default connect(
state => ({
auth: state.Auth,
locale: state.LanguageSwitcher.toJS().language.locale,
selectedTheme: state.ThemeSwitcher.toJS().changeThemes.themeName,
height: state.App.toJS().height
}),
{ logout, toggleAll }
)(App);
I think this should be enough to illustrate my question.
You're not using redux correctly there. You need to keep somewhere in your redux state your active tenant; That information will be your single source of truth and will be shared among your components throughout the app. Every component that will need that information will be connected to that part of the state and won't need to hold an internal state for that information.
Actions, are where you would call your API to get your active tenant information or set a new tenant as active. These actions wil call reducers that will change your redux state (That includes your active tenant information) and those redux state changes will update your app components.
You should probably take some time to read or re-read the redux doc, it's short and well explained !
I'm trying to reset editor content after some action completed using react-draft-wysiwyg editor. All contents cleared by using clearEditorContent method from draftjs-utils. But after clearing contents I can't able to type nothing in editor. Added the code below. Please help to solve this problem.
import React, { Component } from 'react';
import { EditorState, convertToRaw, ContentState } from 'draft-js';
import { clearEditorContent } from 'draftjs-utils'
import { Editor } from 'react-draft-wysiwyg';
import '../../../../node_modules/react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import draftToHtml from 'draftjs-to-html';
import htmlToDraft from 'html-to-draftjs';
export default class RTEditor extends Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
};
this.setDomEditorRef = ref => this.domEditor = ref;
}
onEditorStateChange: Function = (editorState) => {
this.setState({
editorState,
}, () => {
this.props.sendResult(draftToHtml(convertToRaw(this.state.editorState.getCurrentContent())));
});
};
componentWillReceiveProps(nextProps) {
if(nextProps.reset) {
this.reset();
}
}
componentDidMount() {
if(this.props.text) {
const html = `${this.props.text}`;
const contentBlock = htmlToDraft(html);
if (contentBlock) {
const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
const editorState = EditorState.createWithContent(contentState);
this.setState({ editorState, });
}
}
this.domEditor.focusEditor();
}
reset = () => {
let {editorState} = this.state;
editorState = clearEditorContent(editorState);
this.setState({ editorState });
};
render() {
const { editorState } = this.state;
return (
<Editor
ref={this.setDomEditorRef}
editorState={editorState}
wrapperClassName="rte-wrapper"
editorClassName="rte-editor"
onEditorStateChange={this.onEditorStateChange}
toolbarCustomButtons={[this.props.UploadHandler]}
/>
)
}
}
My parent component code is below,
import React, { Component } from 'react'
import { addThreadPost } from '../../../api/thread-api'
import { isEmpty } from '../../../api/common-api'
import RTEditor from './Loadable'
export default class ThreadActivity extends Component {
constructor(props) {
super(props)
this.state = {
clearEditor: false,
threadPost: ''
}
}
setPost = (post) => {
this.setState({ threadPost: post })
}
addThreadPost = () => {
let postText = this.state.threadPost.replace(/<[^>]+>/g, '');
if(!isEmpty(postText)) {
addThreadPost(this.props.match.params.id_thread, this.state.threadPost, this.state.postAttachments).then(response => {
this.setState({
clearEditor: true,
postAttachments: [],
})
});
}
else {
alert("Please enter some text in box.");
}
}
render() {
return [
<div className="commentbox-container">
<div className="form-group">
<div className="form-control">
<RTEditor
ref={node => { this.threadPost = node }}
text={""}
sendResult={this.setPost.bind(this)}
reset={this.state.clearEditor}
/>
<button className="btn-add-post" onClick={this.addThreadPost}>Add Post</button>
</div>
</div>
</div>
]
}
}
Your problem is probably that once you set ThreadActivity's state.clearEditor to true, you never set it back to false. So your this.reset() is getting called every time the component receives props. Which, incidentally, is going to be every time you try to type because you're invoking that this.props.sendResult.
The simplest fix is to make sure you change state.clearEditor back to false once the clearing is done.
Add to ThreadActivity.js:
constructor(props) {
...
this.completeClear = this.completeClear.bind(this);
}
...
completeClear = () => {
this.setState({clearEditor: false});
}
render() {
...
<RTEditor
...
completeClear={this.completeClear}
/>
...
}
And in RTEditor.js:
reset = () => {
let {editorState} = this.state;
editorState = clearEditorContent(editorState);
this.setState({ editorState });
this.props.completeClear();
};