Converting DraftJS class component to Functional component - reactjs

The following Draftjs code is in class component. The plugins like CreateImage, Focus Plugin, and BlockDndPlugin are being imported from the DraftJS. I would be grateful if somebody can convert the class-based react components into Functional based react components...............................................................................................................................................................................................................................................................................................
import React, { Component } from 'react';
import { convertFromRaw, EditorState } from 'draft-js';
import Editor, { composeDecorators } from '#draft-js-plugins/editor';
import createImagePlugin from '#draft-js-plugins/image';
import createFocusPlugin from '#draft-js-plugins/focus';
import createBlockDndPlugin from '#draft-js-plugins/drag-n-drop';
import editorStyles from './editorStyles.module.css';
const focusPlugin = createFocusPlugin();
const blockDndPlugin = createBlockDndPlugin();
const decorator = composeDecorators(
focusPlugin.decorator,
blockDndPlugin.decorator
);
const imagePlugin = createImagePlugin({ decorator });
const plugins = [blockDndPlugin, focusPlugin, imagePlugin];
/* eslint-disable */
const initialState = {
entityMap: {
0: {
type: 'IMAGE',
mutability: 'IMMUTABLE',
data: {
src: '/images/canada-landscape-small.jpg',
},
},
},
blocks: [
{
key: '9gm3s',
text:
'You can have images in your text field which are draggable. Hover over the image press down your mouse button and drag it to another position inside the editor.',
type: 'unstyled',
depth: 0,
inlineStyleRanges: [],
entityRanges: [],
data: {},
},
{
key: 'ov7r',
text: ' ',
type: 'atomic',
depth: 0,
inlineStyleRanges: [],
entityRanges: [
{
offset: 0,
length: 1,
key: 0,
},
],
data: {},
},
{
key: 'e23a8',
text:
'You can checkout the alignment tool plugin documentation to see how to build a compatible block plugin …',
type: 'unstyled',
depth: 0,
inlineStyleRanges: [],
entityRanges: [],
data: {},
},
],
};
/* eslint-enable */
export default class CustomImageEditor extends Component {
state = {
editorState: EditorState.createWithContent(convertFromRaw(initialState)),
};
onChange = (editorState) => {
this.setState({
editorState,
});
};
focus = () => {
this.editor.focus();
};
render() {
return (
<div>
<div className={editorStyles.editor} onClick={this.focus}>
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
plugins={plugins}
ref={(element) => {
this.editor = element;
}}
/>
</div>
</div>
);
}
}

You can use useState and useRef hooks in FC & Modify your component accordingly..
const CustomImageEditor = () => {
const [editorState, setEditorState] = useState(
EditorState.createWithContent(convertFromRaw(initialState))
);
const editor = useRef();
const onChange = (editorStates) => {
setEditorState(editorStates);
};
const focus = () => {
editor.current.focus();
};
return (
<div>
<div className={editorStyles.editor} onClick={focus}>
<Editor
editorState={editorState}
onChange={onChange}
plugins={plugins}
ref={editor}
/>
</div>
</div>
);
};

Related

React Quill - social media link to embed media on rich text editor

I want the react quill rich text editor able to convert link into social media, like this
link: https://www.tiktok.com/#epicgardening/video/7055411162212633903
My RTE Code
import { useCallback, useMemo, useEffect } from 'react';
import ImageResize from 'quill-image-resize-module-react';
import ReactQuill, { Quill } from 'react-quill';
import { message } from 'antd';
import { uploadFiles } from 'utils';
import 'react-quill/dist/quill.bubble.css';
import 'react-quill/dist/quill.snow.css';
import './styles.scss';
Quill.register('modules/imageResize', ImageResize);
const RichTextEditor = ({
editorState,
onChange,
readOnly = false,
setLoading = () => {}
}) => {
window.Quill = Quill;
let quillRef = null; // Quill instance
let reactQuillRef = null; // ReactQuill component
useEffect(() => {
attachQuillRefs();
}, []);
const attachQuillRefs = useCallback(() => {
if (typeof reactQuillRef.getEditor !== 'function') return;
quillRef = reactQuillRef.getEditor();
}, []);
const imageHandler = () => {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.click();
input.onchange = async () => {
const file = input.files[0];
if (file.size > 1500000)
return message.error('Image size exceeded 1.5Mb');
setLoading({ image: true });
const formData = new FormData();
formData.append('image', file);
const fileName = file.name;
const imgUrl = await uploadFiles(file, quillRef);
const range = quillRef.getSelection();
quillRef.insertEmbed(range.index, 'image', imgUrl, 'user');
let existingDelta = quillRef.getContents();
const indexOf = existingDelta.ops.findIndex((eachOps) => {
return eachOps.insert?.image === imgUrl;
});
const selectedOps = existingDelta.ops[indexOf];
if (indexOf !== -1) {
selectedOps.attributes = {};
selectedOps.attributes = { alt: fileName };
}
quillRef.setContents(existingDelta);
setLoading({ image: false });
};
};
const modules = useMemo(
() => ({
toolbar: {
container: [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
['bold', 'italic', 'underline'],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ align: [] }],
['link', 'image'],
['clean'],
[{ color: [] }]
],
handlers: {
image: imageHandler
}
},
imageResize: {
modules: ['Resize', 'DisplaySize']
}
}),
[]
);
return (
<div className="react-quill-wrapper">
<ReactQuill
readOnly={readOnly}
theme={readOnly ? 'bubble' : 'snow'}
ref={(e) => {
reactQuillRef = e;
}}
value={editorState}
modules={modules}
placeholder="Add content of your article!"
onChange={onChange}
/>
</div>
);
};
export { RichTextEditor };
const [editorState, setEditorState] = useState('');
<RichTextEditor
editorState={editorState}
onChange={setEditorState}
setLoading={setLoading}
/>
called by parent like this
I've been working on this for almost a week, I really need help
I expected to have an string HTML output, like this library or image above
My attempts:
Get the social media url typed by user based on link, use that typed link to determined what social media, and use react-social-media-embed to give me an output link image above.
I belive(maybe) that the output from react-social-media-embed is jsx, and I need to convert it to html, and parsed it to string.

How to test setFieldValue from Formik, in a react-slider component

maybe it is a weird question, but I am new to unit testing, and can't wrap my head around this one.
import { Field, FieldInputProps, FieldMetaProps, FormikHelpers, FormikValues } from 'formik';
import Slider from 'components/elements/slider';
import FormError from 'components/elements/formerror';
import { useEffect, useState } from 'react';
interface IScheduleSlider {
min?: number;
max?: number;
title: string;
name: string;
unitLabel: string;
conversions: {
first: {
//eslint-disable-next-line
convertFct: (x: number) => number;
label: string;
};
second: {
//eslint-disable-next-line
convertFct: (x: number) => number;
label: string;
};
};
showError?: boolean;
}
const ScheduleSlider = ({ min = 0, max = 100, title, name, unitLabel, conversions, showError = false }: IScheduleSlider) => {
const [error, setError] = useState(false);
console.log(conversions);
useEffect(() => {
if (showError) {
setError(true);
}
}, [showError]);
const getRealValue = (val: number) => (val > max ? max : val);
return (
<Field name={name}>
{({
field,
form: { setFieldValue, setFieldTouched },
meta,
}: {
field: FieldInputProps<never>;
form: FormikHelpers<FormikValues>;
meta: FieldMetaProps<never>;
}) => (
<div className={`schedule-slider ${error ? 'error' : ''}`} data-testid="schedule-slider-test">
<div className="schedule-slider__top">
<h4>{title}</h4>
{typeof conversions?.first?.convertFct === 'function' && typeof conversions?.second?.convertFct === 'function' && (
<div>
<div className="schedule-slider__top--conversion">
<h5>{conversions?.first?.convertFct(field.value)}</h5>
<span>{conversions?.first?.label}</span>
</div>
<div className="schedule-slider__top--conversion">
<h5>{conversions?.second?.convertFct(field.value)}</h5>
<span>{conversions?.second?.label}</span>
</div>
</div>
)}
</div>
<div className="schedule-slider__bottom">
<Slider
max={Math.ceil(max)}
min={min}
value={Math.ceil(field.value)}
onChange={(val: number) => {
setFieldValue(field?.name, getRealValue(val));
setFieldTouched(field?.name, true);
setError(false);
}}
labels={{ left: `${min} ${unitLabel}`, right: `${max} ${unitLabel}` }}
/>
<div className="schedule-slider__value">
<div>
<h3 data-testid="field-test-value">{field.value}</h3>
<span>{unitLabel}</span>
</div>
</div>
</div>
{error && <FormError meta={meta} />}
</div>
)}
</Field>
);
};
export default ScheduleSlider;
This is my tsx file with the component, I have a ScheduleSlider component which in itself contains a formik component, and a react-slider, which has the onChange prop where setFieldValue and setFieldTouched is.
/* eslint-disable testing-library/no-container */
/* eslint-disable testing-library/no-node-access */
import ScheduleSlider from './scheduleslider';
import * as Formik from 'formik';
import { cleanup, fireEvent, render, screen, waitFor } from '#testing-library/react';
import React from 'react';
import userEvent from '#testing-library/user-event';
import { renderWithRedux } from 'test-utils';
describe('render ScheduleSlider', () => {
const mockFn = jest.fn();
const useFormikContextMock = jest.spyOn(Formik, 'useFormikContext');
beforeEach(() => {
useFormikContextMock.mockReturnValue({
setFieldValue: jest.fn(),
setFieldTouched: jest.fn(),
} as unknown as never);
});
afterEach(() => {
jest.clearAllMocks();
cleanup();
});
const defaultId = 'schedule-slider-test';
const sliderId = 'slider-test';
const fieldId = 'field-test-value';
it('render component with props', async () => {
const props = {
min: 0,
max: 100,
title: 'Test title',
name: 'POWER',
unitLabel: 'kWh',
conversions: {
first: {
convertFct: jest.fn().mockImplementation((x) => x),
label: '%',
},
second: {
convertFct: jest.fn().mockImplementation((x) => x),
label: 'km',
},
},
showError: false,
};
const { container } = renderWithRedux(
<Formik.Formik initialValues={{}} enableReinitialize onSubmit={mockFn}>
<ScheduleSlider {...props} />
</Formik.Formik>
);
// const slider = screen.queryByRole('slider');
const slider = screen.getByTestId(sliderId);
expect(container).toBeVisible();
props.conversions.first.convertFct.mockReturnValue('10');
props.conversions.second.convertFct.mockReturnValue('30');
// expect(slider).toBeVisible();
// expect(slider).toHaveClass('slider__thumb slider__thumb-0');
if (slider) {
slider.ariaValueMin = '1';
slider.ariaValueMax = '100';
// screen.getByTestId(fieldId).textContent = '80';
fireEvent.keyDown(slider, { key: 'ArrowLeft', code: 'ArrowLeft', charCode: 37 });
}
// expect(useFormikContextMock).toBeCalled();
// console.log(slider?.ariaValueMin);
// console.log(screen.getByTestId(fieldId).textContent);
console.log(props.conversions.second.convertFct.mock.results);
console.log(container.textContent);
});
it('render with default min, max, showError value', () => {
const props = {
min: undefined,
max: undefined,
title: 'test title',
name: 'schedule',
unitLabel: 'test unit',
conversions: {
first: {
convertFct: jest.fn(),
label: '%',
},
second: {
convertFct: jest.fn(),
label: 'km',
},
},
showError: true,
};
render(
<Formik.Formik initialValues={{}} enableReinitialize onSubmit={mockFn}>
<ScheduleSlider {...props} />
</Formik.Formik>
);
expect(screen.getByTestId(defaultId)).toBeVisible();
});
it('checks for onChange values', () => {
const props = {
min: 0,
max: 100,
title: 'test title',
name: 'schedule',
unitLabel: 'test unit',
conversions: {
first: {
convertFct: jest.fn(),
label: '%',
},
second: {
convertFct: jest.fn(),
label: 'km',
},
},
showError: undefined,
};
render(
<Formik.Formik initialValues={{}} enableReinitialize onSubmit={mockFn}>
<ScheduleSlider {...props} />
</Formik.Formik>
);
expect(screen.getByTestId(defaultId)).toBeVisible();
});
});
And this is my test file, I could render the component, and some of the branches, functions, statemnts are covered, but don't know how to test setFieldValue. Tried to fire events, but I am making some errors and can't see where. Anybody has any idea how to start with this. Sorry for the comments, console.log-s but I was tryng all kinds of solutions

Using DraftJS in a Functional Component

I am trying to implement DraftJS within an existing functional component and I am unable to figure out how to do this. It appears that all of the documentation and user-submitted content refers to class components.
I try to set it up using the following:
import { Editor } from "react-draft-wysiwyg";
import { EditorState } from 'draft-js'
export default function myFunctionalComponent() {
const [editorState, setEditorState] = useState(EditorState.createEmpty())
return(
<Editor
editorState={editorState}
onChange={setEditorState}
/>
)
}
However, unfortunately, I get this error in the console:
Warning: Can't call setState on a component that is not yet mounted.
This is a no-op, but it might indicate a bug in your application.
Instead, assign to this.state directly or define a state = {};
class property with the desired state in the r component.
Is there a way to make this work in a functional component?
As my very first answer in StackOverflow. :)
I took the example from https://github.com/facebook/draft-js/blob/main/examples/draft-0-10-0/rich/rich.html and converted it into a functional components 'RTEditor', .. .
Use the component with setContent as a prop. It takes the function to update parent elements state from useState
const [content, setContent] = useState<any>({})
...
<RTEditor setContent={setContent} />
RTEditor.tsx
import React, { useState, useRef } from 'react'
import {
Editor,
EditorState,
RichUtils,
getDefaultKeyBinding,
ContentBlock,
DraftHandleValue,
convertFromHTML,
convertFromRaw,
convertToRaw,
ContentState,
RawDraftContentState,
} from 'draft-js'
import 'draft-js/dist/Draft.css'
import BlockStyleControls from './BlockStyleControls'
import InlineStyleControls from './InlineStyleControls'
type Props = {
setContent: (state: RawDraftContentState) => void
}
const RTEditor = ({ setContent }: Props) => {
const editorRef = useRef(null)
const [editorState, setEditorState] = useState(EditorState.createEmpty())
const styleMap = {
CODE: {
backgroundColor: 'rgba(0, 0, 0, 0.05)',
fontFamily: '"Inconsolata", "Menlo", "Consolas", monospace',
fontSize: 16,
padding: 2,
},
}
const getBlockStyle = (block: ContentBlock) => {
switch (block.getType()) {
case 'blockquote':
return 'RichEditor-blockquote'
default:
return ''
}
}
const onChange = (state: EditorState) => {
setEditorState(state)
setContent(convertToRaw(editorState.getCurrentContent()))
}
const mapKeyToEditorCommand = (e: any): string | null => {
if (e.keyCode === 9 /* TAB */) {
const newEditorState = RichUtils.onTab(e, editorState, 4 /* maxDepth */)
if (newEditorState !== editorState) {
onChange(newEditorState)
}
return null
}
return getDefaultKeyBinding(e)
}
const handleKeyCommand = (
command: string,
editorState: EditorState,
eventTimeStamp: number
): DraftHandleValue => {
const newState = RichUtils.handleKeyCommand(editorState, command)
if (newState) {
onChange(newState)
return 'handled'
}
return 'not-handled'
}
const toggleBlockType = (blockType: string) => {
onChange(RichUtils.toggleBlockType(editorState, blockType))
}
const toggleInlineStyle = (inlineStyle: string) => {
onChange(RichUtils.toggleInlineStyle(editorState, inlineStyle))
}
return (
<>
<BlockStyleControls
editorState={editorState}
onToggle={toggleBlockType}
/>
<InlineStyleControls
editorState={editorState}
onToggle={toggleInlineStyle}
/>
<Editor
ref={editorRef}
editorState={editorState}
placeholder='Tell a story...'
customStyleMap={styleMap}
blockStyleFn={(block: ContentBlock) => getBlockStyle(block)}
keyBindingFn={(e) => mapKeyToEditorCommand(e)}
onChange={onChange}
spellCheck={true}
handleKeyCommand={handleKeyCommand}
/>
</>
)
}
export default React.memo(RTEditor)
BlockStyleControls.tsx
import React from 'react'
import { EditorState } from 'draft-js'
import StyleButton from './StyleButton'
const BLOCK_TYPES = [
{ label: 'H1', style: 'header-one' },
{ label: 'H2', style: 'header-two' },
{ label: 'H3', style: 'header-three' },
{ label: 'H4', style: 'header-four' },
{ label: 'H5', style: 'header-five' },
{ label: 'H6', style: 'header-six' },
{ label: 'Blockquote', style: 'blockquote' },
{ label: 'UL', style: 'unordered-list-item' },
{ label: 'OL', style: 'ordered-list-item' },
{ label: 'Code Block', style: 'code-block' },
]
type Props = {
editorState: EditorState
onToggle: (bockType: string) => void
}
const BlockStyleControls = ({ editorState, onToggle }: Props) => {
const selection = editorState.getSelection()
const blockType = editorState
.getCurrentContent()
.getBlockForKey(selection.getStartKey())
.getType()
return (
<div className='RichEditor-controls'>
{BLOCK_TYPES.map((type) => (
<StyleButton
key={type.label}
active={type.style === blockType}
label={type.label}
onToggle={onToggle}
style={type.style}
/>
))}
</div>
)
}
export default React.memo(BlockStyleControls)
InlineStyleControls.tsx
import React from 'react'
import { EditorState } from 'draft-js'
import StyleButton from './StyleButton'
const INLINE_STYLES = [
{ label: 'Bold', style: 'BOLD' },
{ label: 'Italic', style: 'ITALIC' },
{ label: 'Underline', style: 'UNDERLINE' },
{ label: 'Monospace', style: 'CODE' },
]
type Props = {
editorState: EditorState
onToggle: (bockType: string) => void
}
const InlineStyleControls = ({ editorState, onToggle }: Props) => {
const currentStyle = editorState.getCurrentInlineStyle()
return (
<div className='RichEditor-controls'>
{INLINE_STYLES.map((type) => (
<StyleButton
key={type.label}
active={currentStyle.has(type.style)}
label={type.label}
onToggle={onToggle}
style={type.style}
/>
))}
</div>
)
}
export default React.memo(InlineStyleControls)
StyleButton.tsx
import React from 'react'
type Props = {
active: boolean
style: string
label: string
onToggle: (bockType: string) => void
}
const StyleButton = ({ active, style, label, onToggle }: Props) => {
const _onToggle = (e: any) => {
e.preventDefault()
onToggle(style)
}
const className = 'RichEditor-styleButton'
return (
<button
className={className + `${active ? ' RichEditor-activeButton' : ''}`}
onClick={_onToggle}
>
{label}
</button>
)
}
export default React.memo(StyleButton)
Sry for not covering all typings. Hope that helps.
I was able to solve this using React useCallback hook and it works for me.
import { EditorState } from 'draft-js'
export default function myFunctionalComponent() {
const [editorState, setEditorState] = useState(EditorState.createEmpty())
const onEditorStateChange = useCallback(
(rawcontent) => {
setEditorState(rawcontent.blocks[0].text);
},
[editorState]
);
return(
<Editor
placeholder="Tell a story..."
onChange={onEditorStateChange}
/>
)
}

onImageLoad callback react js

Im using react-image-gallery: https://www.npmjs.com/package/react-image-gallery
Im trying to set a useState variable on onImageLoad, but its not working.
the docs say to use a callback, and I tried using a callback but I don't think I was doing it correctly for functional components.
Could someone show me how to create the proper callback to get the onImageLoad prop?
Docs say..... onImageLoad: Function, callback(event)
import React, { useEffect, useState} from "react";
import ImageGallery from 'react-image-gallery';
import "react-image-gallery/styles/css/image-gallery.css";
const Job = (props) => {
const {job} = props;
const [image, setImage] = useState([]);
const [showImages, setShowImages] = useState(false);
useEffect(() => {
async function onLoad() {
try {
const downloadedImage = await getImage(job.jobId);
setImage(downloadedImage);
} catch (e) {
alert(e);
}
}
onLoad();
}, []);
return (
{showImages ? (
<div style={{width: "95%"}}>
<ImageGallery items={image} onImageLoad={() => setShowImages(true)}/>
</div>
):(
<div>
<IonSpinner name="crescent" />
</div>
)}
);
Example given from package website
import React from 'react';
import ReactDOM from 'react-dom';
import ImageGallery from '../src/ImageGallery';
const PREFIX_URL = 'https://raw.githubusercontent.com/xiaolin/react-image-gallery/master/static/';
class App extends React.Component {
constructor() {
super();
this.state = {
showIndex: false,
showBullets: true,
infinite: true,
showThumbnails: true,
showFullscreenButton: true,
showGalleryFullscreenButton: true,
showPlayButton: true,
showGalleryPlayButton: true,
showNav: true,
isRTL: false,
slideDuration: 450,
slideInterval: 2000,
slideOnThumbnailOver: false,
thumbnailPosition: 'bottom',
showVideo: {},
};
this.images = [
{
thumbnail: `${PREFIX_URL}4v.jpg`,
original: `${PREFIX_URL}4v.jpg`,
embedUrl: 'https://www.youtube.com/embed/4pSzhZ76GdM?autoplay=1&showinfo=0',
description: 'Render custom slides within the gallery',
renderItem: this._renderVideo.bind(this)
},
{
original: `${PREFIX_URL}image_set_default.jpg`,
thumbnail: `${PREFIX_URL}image_set_thumb.jpg`,
imageSet: [
{
srcSet: `${PREFIX_URL}image_set_cropped.jpg`,
media : '(max-width: 1280px)',
},
{
srcSet: `${PREFIX_URL}image_set_default.jpg`,
media : '(min-width: 1280px)',
}
]
},
{
original: `${PREFIX_URL}1.jpg`,
thumbnail: `${PREFIX_URL}1t.jpg`,
originalClass: 'featured-slide',
thumbnailClass: 'featured-thumb',
description: 'Custom class for slides & thumbnails'
},
].concat(this._getStaticImages());
}
_onImageLoad(event) {
console.debug('loaded image', event.target.src);
}
render() {
return (
<section className='app'>
<ImageGallery
ref={i => this._imageGallery = i}
items={this.images}
onImageLoad={this._onImageLoad}
/>
</section>
);
}
}
ReactDOM.render(<App/>, document.getElementById('container'));

react-ace + flexlayout-react: Ace editor keeps resetting

I have a FlexLayout (from flexlayout-react) which contains an AceEditor (from react-ace). For testing I added a Test component as well.
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import FlexLayout from 'flexlayout-react';
import AceEditor from 'react-ace';
// display an Ace editor (here with fixed size)
const Editor = () => {
return (
<AceEditor
width="200px"
height="200px"
value="foo"
/>
);
}
// an increment button, just something simple stateful
const Test = () => {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
{count}
</button>
</div>
);
};
// two columns for editor and test component
const model = FlexLayout.Model.fromJson({
global: {},
borders: [],
layout: {
type: 'row',
weight: 50,
children: [
{
type: 'tabset',
weight: 50,
selected: 0,
children: [
{
type: 'tab',
name: 'A',
component: 'editor',
},
],
},
{
type: 'tabset',
weight: 50,
selected: 0,
children: [
{
type: 'tab',
name: 'B',
component: 'test',
},
],
},
],
},
});
const factory = node => {
switch (node.getComponent()) {
case 'editor': {
return <Editor />;
}
case 'test': {
return <Test />;
}
default:
return null;
}
}
// display the flex layout
const Ide = () => {
return (
<FlexLayout.Layout
model={model}
factory={factory}
/>
);
};
// render everything
ReactDOM.render(
<Ide />,
document.getElementById('react-container')
);
So what's going on?
Whenever the FlexLayout state changes (focus changed, dragging the divider, changing width), the text of the Ace editor is reset to foo. In contrast, the value of Test is preserved. Without the FlexLayout, the problem goes away.
So there seems to be a curious interaction between the two components, but I'm too inexperienced with React to figure it out. How would I go about debugging this? What are common avenues of approach with such an issue? Or any concrete ideas where the error is in this specific situation?

Resources