CKEditor rerenders after every State/ Props Change - javascript

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>
</>
)
}

Related

Using handleChange inside a default function instead of a class

I would like to use a Dropdown module with Semantic UI and react. The issue is all examples provided online use a default class App extends Component however I want to export default function App(). When I do this the I get a parsing error for the render() section, requiring a semicolon.
The below works very well but how can I implement it if the export was a default function instead?
import React, { useState, Component } from 'react'
import { Dropdown, Grid, Segment } from 'semantic-ui-react'
export default class DropdownExampleControlled extends Component {
state = {}
handleChange = (e, { value }) => this.setState({ value })
render() {
const { value } = this.state
return (
<Dropdown
onChange={this.handleChange}
options={options}
placeholder='Choose an option'
selection
value={value}
/>
)
}
}
Writing const infront of the handleChange did not fix anything, it just made the "value" undefined later on. I am very unsure of how to use this because I am new to JS. Any help is greatly appreciated.
When using function components rather than class components you have to utilise the hooks in React.
Heres how your code would look in a function component
export default const DropdownExampleControlled = () => {
const [yourState, setYourState] = useState({});
// yourState being the name of the state and {} being your initial state
const handleChange = (e, {value}) => {setYourState(value)}
return (
<Dropdown
onChange={handleChange}
options={options}
placeholder='Choose an option'
selection
value={yourState}
/>
)
}
Hooks are the newer way of persisting data, handling re-renders & more in modern React. If you want to learn more about how you can use hooks, here's a link to the docs https://reactjs.org/docs/hooks-intro.html

Editor is uneditable and options appear vertically

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.

Next.js renders element twice

On the first image you can see next.js rendered this element twice
I used tables and thought that it is because of them but then I tried to remove tables and put jut and it still renders twice so I don't know what it can be.
Next.js does not renders only that element but the first from this object
const Sections = {
1: Locations,
0: Departments, // here it will render this one twice
2: Managers,
3: JobTitles,
};
Maybe it has something to do with useState and my statemanagment in this code below
Component that renders twice.
const Locations = () => {
return <div>hdjsad</div>;
};
// Tab Sections
import Locations from ''
import Departments from ''
import Managers from ''
import JobTitles from ''
import Icons from "../../Icons";
import TabItem from "./TabItem";
const tabs_text = ["Locations", "Departments", "Managers", "Job Titles"];
const Sections = {
0: Locations, // THIS IS THE COMPONENT WHICH RENDERS TWICE
1: Departments,
2: Managers,
3: JobTitles,
};
const SettingsTab = () => {
const [active, setActive] = useState<number>(0);
const select = useCallback((id: number) => {
return () => setActive(id);
}, []);
const ActiveSection = useMemo(() => Sections[active], [active]);
return (
<section className={"mb-[24px]"}>
<header
className={"w-full flex items-center mb-[34px] pl-[24px] pr-[12px]"}
>
<div className={"flex space-x-[8px] !mb-0 overflow-x-scroll"}>
{tabs_text.map((tab_text, i) => {
return (
<div onClick={select(i)} key={i}>
<TabItem active={+active === i}>{tab_text}</TabItem>
</div>
);
})}
</div>
<ImportLocationsAndFilter />
</header>
<ActiveSection />
</section>
);
};
APP.js
import { AppProps } from "next/app";
import "antd/dist/antd.css";
import "../styles/global.css";
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default MyApp;
I can't comment yet so I'll do it here. I know react says in the official docs to never rely on UseMemo or Use callback for functionality. It says you should create your application so it works without them, and then add them for performance reasons. What would happen if you took the useMemo out and put
ActiveSelection = Selections[active]
I don't think it'll fix your problem but it might give you more insight into what's causing it.
I just imported my tabs dynamically and set SSR: false.
It has to do something with next.js hydration.
https://nextjs.org/docs/advanced-features/dynamic-import
dynamic(
() => import(""),
{
ssr: false,
}
);
It's strange behaviour / bug related to next.js ssr to fix it wrap your Component in a div like this:
function MyApp({ Component, pageProps }: AppProps) {
return <div id=#root><Component {...pageProps} /></div>;
}

Learning React: how to useRef in custom component?

I'm learning React and I don't think I understand the concept of useRef properly. Basically, I want to include some tags in tagify input field when a user clicks on a chip that is rendered outside the input box.
My idea is to do something like this (App.js):
import Chip from '#material-ui/core/Chip';
import Tagify from "./Tagify"
...
class App extends React.Component {
...
const { error, isLoaded, quote, tags } = this.state; //tags comes from the server
var tagify = <Tagify tags={tags} />
const addTagOnChipClick = (tag) => {
tagify.addTag(tag)
};
const chips = tags.map(tag => (
<span key={tag.name} className="chips">
<Chip
label={tag.name}
variant="outlined"
onClick={addTagOnChipClick(tag)}
clickable
/>
</span>
))
...
}
The tagify documentation says that
To gain full access to Tagify's (instance) inner methods, A custom ref can be used: <Tags tagifyRef={tagifyRef} ... />
My attempt to gain access to these inner methods was to use useRef (Tagify.js):
import Tags from '#yaireo/tagify/dist/react.tagify'
import '#yaireo/tagify/dist/tagify.css'
export default function Tagify(tags) {
const tagifyRef = useRef()
return (
<Tags
tagifyRef={tagifyRef}
placeholder='Filter by tags...'
whitelist={tags.tags}
/>
)
}
However, tagifyRef.current is undefined. What I'm doing wrong? There's another way to access the inner methods?
Thank you very much!
When are you accessing the ref? Make sure you access the ref only after the component has mounted i.e. in a useEffect:
import Tags from '#yaireo/tagify/dist/react.tagify'
import '#yaireo/tagify/dist/tagify.css'
export default function Tagify(tags) {
const tagifyRef = useRef()
React.useEffect(() => {
console.log(tagifyRef.current)
}, [])
return (
<Tags
tagifyRef={tagifyRef}
placeholder='Filter by tags...'
whitelist={tags.tags}
/>
)
}

React Context API with multiple values performance

I'm using the React Context API to store many global state values (around 10 and probably more will be needed) and many components are using them. Unfortunately whenever any of the values change, all components using the useContext hook have to rerender. My current solution is to use useMemo for the return value of the components and useCallback for any complex functions and inside custom hooks I have. This addresses most of my performance concerns, but having to use the useMemo and useCallback all the time is quite annoying and missing one is quite easy. Is there a more professional way to do it?
Here's an example based on my code:
GlobalStateContext.js
import React, { useState } from 'react'
const GlobalStateContext = React.createContext({ })
export const GlobalStateProvider = ({ children }) => {
const [config, setConfig] = useState({
projectInfo: ''
})
const [projectFile, setProjectFile] = useState('./test.cpp')
const [executionState, setExecutionState] = useState("NoProject")
return (
<GlobalStateContext.Provider
value={{
executionState,
config,
projectFile,
setExecutionState,
setConfig,
setProjectFile,
}}
>
{children}
</GlobalStateContext.Provider>
)
}
export default GlobalStateContext
Example.jsx
import React, { useContext } from 'react'
import GlobalStateContext from '../utils/GlobalStateContext.js'
export default Example = () => {
const {
executionState,
setExecutionState,
} = useContext(GlobalStateContext)
return useMemo(
() => (
<div>
The current execution state is: {executionState}
<br />
<button onClick={() => setExecutionState('Running')}>Running</button>
<button onClick={() => setExecutionState('Stopped')}>Stopped</button>
<button onClick={() => setExecutionState('Crashed')}>Crashed</button>
</div>
),
[
executionState,
setExecutionState,
]
)
}
Currently, this problem is unavoidable with context. There is an open RFC for context selectors to solve this, but in the meantime, some workarounds are useContextSelector and Redux, both of which prevent a subscribing component from rendering if the data it's reading did not change.

Categories

Resources