I am trying to use draft js to present a wysiwyg editor.
When I load the component, I am unable to edit anything and the options are coming up vertically.
Expecting it to appear horizontally. What am I doing wrong?
This is how it looks currently.
Implementation.
import React from 'react';
import { EditorState, convertToRaw } from 'draft-js';
import draftToHtml from 'draftjs-to-html';
import dynamic from 'next/dynamic'
import { EditorProps } from 'react-draft-wysiwyg'
const TextEditor = () => {
// getting window undefined error thus importing this dynamically
const Editor = dynamic<EditorProps>(
() => import('react-draft-wysiwyg').then((mod) => mod.Editor),
{ ssr: false }
)
const [editorState, setEditorState] = React.useState(
EditorState.createEmpty()
);
return (
<div>
<Editor
editorState={editorState}
wrapperClassName="wrapper"
editorClassName="editor"
onEditorStateChange={() => setEditorState(editorState)}
/>
<textarea
disabled
value={draftToHtml(convertToRaw(editorState.getCurrentContent()))}
/>
</div>
);
}
export default TextEditor
One thing is wrong for sure, you wrote:
onEditorStateChange={() => setEditorState(editorState)}
It should be:
onEditorStateChange={(newEditorState) => setEditorState(newEditorState)}
// or shorter form:
onEditorStateChange={setEditorState}
Now regarding the style, two thing to look into.
double check that you have included the css somewhere, like import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'; but check against your bundler config, not sure about the path on your machine.
It looks like you’re trying to customize the style with wrapperClassName="wrapper" editorClassName="editor". Try remove them for now and see if them interfere. I suspect this is part of the cause.
Related
currently I am working on a project with Next.js and CKEditor 5. I created an Editor-Component which I want to use on a page. Since I need the value of the input on the parent page, I am using a state and setState as props.
My Code looks like this:
Page:
import dynamic from "next/dynamic";
import { useState } from 'react';
export default function Create() {
const Editor = dynamic(() => import("../components/MyEditor"), { ssr: false });
const [text, setText] = useState("")
const handleTextInput = (textInput) => {
setText(textInput)
}
return (
<>
<div key="editor1div">
<Editor key="editor1" handleInput={handleTextInput} data={text} />
</div>
</>
)
}
Editor-Component:
import Editor from '../../ckeditor5-custom-build/build/ckeditor'
import { CKEditor } from '#ckeditor/ckeditor5-react'
import '../../ckeditor5-custom-build/build/translations/de';
const MyEditor = (props) => {
const editorConfiguration = {
toolbar: {
items: ['bold', 'italic', 'underline', '|', 'undo', 'redo'],
}
};
return (
<>
<CKEditor
editor={Editor}
config={editorConfiguration}
data={props.data}
onChange={(event, editor) => {
props.handleInput(editor.getData())
}}
/>
</>
);
}
export default MyEditor
My Problem:
The Editor gets rerendered everytime, a key is hit. That means it also loses focus, which leads to a bad user experience. As far as I understand, setting a key to the editor should prevent rerendering on every props change, but it did not work. As suggested in other similar questions, I tried uuid's v4()-Method as key, but also this did not solve the problem.
The solution I wish for:
In the best case, the editor would not rerender on every new char that is entered and just stay the same. Alternatively, if that is not possible, I could also work with a solution in which I manually set the focus to the editor or where the state is not permanently updated, but only on a button click on the Page itself.
Did anybody have similar problems or knows a solution?
Kind regards
Robert
I found the solution my self. For everybody with similar problems: the reason is the dynamic import inside the component. If the dynamic import is outside the component, it works:
import dynamic from "next/dynamic";
import { useState } from 'react';
const Editor = dynamic(() => import("../components/MyEditor"), { ssr: false });
export default function Create() {
const [text, setText] = useState("")
const handleTextInput = (textInput) => {
setText(textInput)
}
return (
<>
<div key="editor1div">
<Editor key="editor1" handleInput={handleTextInput} data={text} />
</div>
</>
)
}
I am trying to write the tests for the NavBar component (using react-native-testing-library) that has several buttons that are basically just icons (using ui-kitten for react native). So I can't get these buttons by text (as there is none) but other methods didn't work for me either (like adding accesibilityLabel or testID and then getting by the label text / getting by test ID). Any ideas what I am doing wrong?
// NavBar.tsx
import React from 'react';
import {View, StyleSheet} from 'react-native';
import {HomeBtn, SaveBtn} from '../components/buttons';
import UserSignOut from './UserSignOut';
const NavBar = ({
navigation,
pressHandlers,
}) => {
return (
<View style={styles.navBar}>
<View>
<HomeBtn navigation={navigation} />
<SaveBtn pressHandler={pressHandlers?.saveBtn ?? undefined} />
</View>
<UserSignOut />
</View>
);
};
export default NavBar;
// HomeBtn.tsx
import React from 'react';
import {Button} from '#ui-kitten/components';
import {HomeIcon} from '../shared/icons';
import styles from './Btn.style';
export const HomeBtn = ({navigation}: any) => {
return (
<Button
accesibilityLabel="home button"
style={styles.button}
accessoryLeft={props => HomeIcon(props, styles.icon)}
onPress={() => navigation.navigate('Home')}
/>
);
};
// NavBar.test.tsx
import React from 'react';
import {render, screen} from '#testing-library/react-native';
import * as eva from '#eva-design/eva';
import {RootSiblingParent} from 'react-native-root-siblings';
import {EvaIconsPack} from '#ui-kitten/eva-icons';
import {ApplicationProvider, IconRegistry} from '#ui-kitten/components';
import NavBar from '../../containers/NavBar';
describe('NavBar', () => {
const navBarContainer = (
<RootSiblingParent>
<IconRegistry icons={EvaIconsPack} />
<ApplicationProvider {...eva} theme={eva.light}>
<NavBar />
</ApplicationProvider>
</RootSiblingParent>
);
it('should render the buttons', async () => {
render(navBarContainer);
// this test fails (nothing is found with this accesibility label)
await screen.findByLabelText('home button');
});
});
Query predicate
The recommended solution would be to use:
getByRole('button', { name: "home button" })
As it will require both the button role, as well as check accessibilityLabel with name option.
Alternative, but slightly less expressive way would be to use:
getByLabelText('home button')
This query will only check accessibilityLabel prop, which also should work fine.
Why is query not matching
Since you're asking why the query is not working, that depends on your test setup. It seems that you should be able to use sync getBy* query and do not need to await findBy* query, as the HomeBtn should be rendered without waiting for any async action.
What might prevent that test from working could be incorrect mocking of any of the wrapping components: RootSiblingParent, ApplicationProvider, they might be "consuming" children prop without rendering it. In order to diagnose the issue you can use debug() function from RNTL to inspect the current state of rendered components. You can also run your tests on render(<NavBar />) to verify that.
Does await screen.findByA11yLabel('home button') work? It should match the accessibilityLabel prop.
I am trying to use ace to create an sql editor, but I need a unique highlight. So, I need to create a custom mode that inherits from the existing sql mode.
The issue is, because ace uses window, and nextjs is ssr, I am unable to create a custom mode using ace's tutorials as I get the window is not defined error in the .ts file.
I can get around this error in the .tsx file by importing it with next/dynamic and disabling ssr for that component, but for the mode I am stumped.
SqlMode.js (it's not .ts because ace typing just doesn't work for me)
import ace from 'ace-builds/src-noconflict/ace';
import 'ace-builds/src-noconflict/mode-sql'
export class SqlHighlightRules extends window.ace.acequire('ace/mode/text_highlight_rules').TextHighlightRules {
constructor() {
super();
this.$rules.start.unshift({
token: 'text-orange-main',
regex: '{{*.}}',
})
}
}
export class SqlMode extends window.ace.acequire('ace/mode/sql').Mode {
constructor() {
super()
this.HighlightRules = SqlHighlightRules;
}
}
The compilation fails in this file, as it uses window, which is undefined.
Editor.tsx
import { useEffect, useRef, useState } from 'react';
import AceEditor from 'react-ace';
import 'ace-builds/src-noconflict/ace';
import 'ace-builds/src-noconflict/theme-tomorrow';
import { SqlMode } from './config/SqlMode';
import 'ace-builds/src-noconflict/ext-language_tools';
const styles = { borderRadius: 8 };
export default function Editor() {
const editor = useRef<AceEditor>();
const [code, setCode] = useState<string>('');
useEffect(() => {
const mode = new SqlMode();
//#ts-ignore
editor.current.editor.getSession().setMode(mode);
}, []);
return (
<AceEditor
ref={editor}
theme="tomorrow"
value={code}
onChange={setCode}
enableBasicAutocompletion
enableLiveAutocompletion
style={styles}
/>
);
}
This file works if used through next/dynamic with { ssr: false } and without the custom mode. But as soon as I use the custom mode, errors.
sql.page.tsx
import dynamic from 'next/dynamic';
const Editor = dynamic(() => import('../components/editor/Editor'), { ssr: false });
export default function SqlEditor() {
return (
<div className="w-full h-full flex justify-center items-center">
<Editor />
</div>
);
}
I would like to be able to create a custom highlight for the sql mode, while working with nextjs. If there is a solution I am not aware of, I'd be happy to learn.
Alternatively if there is another FE editor I could use that does largely the same thing as ace, that could also be useful.
Thank you.
I was getting the 'window is not defined' error when importing react-component-export-image so I used a dynamic import to get around that. I don't get that error anymore but now I get 'exportComponentAsPNG(componentRef) is not a function'. Is there a better way to deal with the 'window is not defined' error or a way to use the function I am importing dynamically? If not, is there a different npm library that works to generate an image from a react component?
import React, { useRef } from 'react'
// import { exportComponentAsPNG } from 'react-component-export-image' *This gave window not defined error so I used dynamic import*
import dynamic from 'next/dynamic'
import ProductCard from '../ProductCard/ProductCard.component'
import Button from '../Button/Button.component'
const { exportComponentAsPNG } = dynamic(
() => import('react-component-export-image'),
{
ssr: false
}
)
const Plaque = () => {
const componentRef = useRef()
// eslint-disable-next-line react/display-name
const ComponentToPrint = React.forwardRef((props, ref) => {
return (
<div ref={ref}>
<ProductCard />
</div>
)
})
return (
<ComponentToPrint ref={componentRef} />
<button onClick={() => exportComponentAsPNG(componentRef)}> // "Error: exportComponentAsPNG is not a function"
Export As PNG
</button>
)
}
export default Plaque
next/dynamic is used to dynamically import React components, not regular JavaScript functions or libraries.
For that, you can use a regular dynamic import on exportComponentAsPNG inside the onClick callback.
<button onClick={async () => {
const { exportComponentAsPNG } = await import('react-component-export-image')
exportComponentAsPNG(componentRef)
}}>
The exportComponentAsPNG function needs access to window which is undefined with server side rendering. I was able to fix the issue by dynamically importing the Plaque component that used exportComponentAsPNG to the page where it is called with sever side rendering set to 'false'.
import dynamic from 'next/dynamic'
const Plaque = dynamic(() => import('../compnonents/Plaque'), {
ssr: false
})
const Page = () => {
return <Plaque />
}
export default Page
Now that the component is no longer using SSR I was able to import and use the function normally.
import { exportComponentAsPNG } from 'react-component-export-image'
Here you can find the documentation for the library: https://www.npmjs.com/package/react-component-export-image
I just discovered PrismJs and it looks perfect. But for some reason, it doesn't highlight my code in following component :
import { useState, useEffect } from "react";
import Prism from "prismjs";
export default function EditCode() {
const [content, setContent] = useState("");
useEffect(() => {
Prism.highlightAll();
}, []);
useEffect(() => {
Prism.highlightAll();
}, [content]);
return (
<section className="codeGlobalContainer">
<textarea
className="codeInput"
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<pre className="codeOutput">
<code className={`language-javascript`}>{content}</code>
</pre>
</section>
);
}
Is there anything missing to make it work ?
It's not specified on there npm page, but you need to download a themed CSS on there official site : PrismsJS
Then, you just move the CSS file to your directory and import it in your component as usual :
import "../../styles/prism.css";
as #FlowRan mentioned you need to import any theme you want to use
but
Note: you do not need to download the themes separately as they come with the package.
Import your theme in your file by using the import statement from-
'prismjs/themes/prism-{theme-name}.css';