I am using React Quill (https://github.com/zenoamaro/react-quill) as a rich text editor in my React project.
Im running into an issue when inserting the below html span element to the editor with an ID:
<span id='incInsert'></span>
The value of the text editor is contained within React State and when console.logging state i can see the span element in there:
However, the span element doesnt exist when inspecting via chrome dev tools and thus in the DOM.
The reason why I need this element to exist in the DOM is because i need to use document.getElementById('incInsert') to insert HTML into the span element which is done later in my submit function.
TIA
I had the same problem, I solved it as follows:
import React, { useState, useRef } from "react";
import ReactQuill, { Quill } from "react-quill"; // ES6
import "react-quill/dist/quill.snow.css";
const Inline = Quill.import("blots/inline");
function MyComponent() {
const [content, setContent] = useState("");
const reactQuillRef = useRef(null);
const createElementWithClassName = () => {
class SpanBlock extends Inline {
static create() {
let node = super.create();
node.setAttribute("class", "spanblock");
node.setAttribute("id", "myId")
return node;
}
}
SpanBlock.blotName = "spanblock";
SpanBlock.tagName = "div";
Quill.register(SpanBlock);
const div = document.createElement("div");
var quill = new Quill(div);
quill.setContents([
{
insert: "hello",
attributes: {
spanblock: true,
},
},
]);
const result = quill.root.innerHTML;
console.log(result);
return result;
};
const buttonClick = () => {
const quill = reactQuillRef.current.getEditor();
const oldHtml = quill.root.innerHTML;
const newElement = createElementWithClassName();
const newHtml = oldHtml + newElement;
setContent(newHtml);
};
return (
<div>
<ReactQuill
ref={reactQuillRef}
modules={{
toolbar: [
[{ font: [] }, { size: ["small", false, "large", "huge"] }], // custom dropdown
["bold", "italic", "underline", "strike"],
[{ color: [] }, { background: [] }],
[{ script: "sub" }, { script: "super" }],
[{ header: 1 }, { header: 2 }, "blockquote", "code-block"],
[
{ list: "ordered" },
{ list: "bullet" },
{ indent: "-1" },
{ indent: "+1" },
],
[{ direction: "rtl" }, { align: [] }],
["link", "image", "video", "formula"],
["clean"],
],
}}
value={content}
onChange={(content) => {
setContent(content);
}}
/>
<button onClick={buttonClick}>click me</button>
</div>
);
}
export default MyComponent;
Related
I am using react quill to set an editor. I want to prepopulate the editor with data from my API, However when I use the defaultValue it does not render. I can console.log the data from the API no problem. Here is the code.....
const GET_ISP_ENTRY = gql`
query IspListEntry($ispListEntryId: ID!) {
ispListEntry(id: $ispListEntryId) {
_id
displayName
contactFirstName
contactLastName
contactTitle
lastUpdated
onlineService
onlineAttn
address
city
state
zipCode
country
phoneNumber
extension
mobileNumber
faxNumber
email
website
referTo
notes
previous
}
}
`;
const UPDATE_ISP_ENTRY = gql`
mutation UpdateISPEntry($ispListEntryUpdateId: ID!, $input: UpdateISPEntry) {
ispListEntryUpdate(id: $ispListEntryUpdateId, input: $input) {
displayName
}
}
`;
const UpdateISPEntry = () => {
const [formValues, setFormValues] = useState();
const [urlId, setUrlId] = useState('');
const [notesFromAPI, setNotesFromAPI] = useState();
const [previousState, setPreviousState] = useState();
const [getIsp, { data, loading, error }] = useLazyQuery(GET_ISP_ENTRY, {
variables: {
ispListEntryId: urlId
},
onCompleted: () => {
setNotesFromAPI(data && data.ispListEntry.notes);
},
onError: () => {
toast.error(error);
}
});
console.log(loading ? 'Loading...' : notesFromAPI);
const [
submitValues,
{ data: successful, loading: successLoading, error: loadingError }
] = useMutation(UPDATE_ISP_ENTRY, {
onError: () => {
toast.error(`There was an error ${loadingError}`);
}
});
const params = useLocation();
const path = params.pathname;
const pathSplit = path.split('/')[2];
const [notesState, setNotesState] = useState();
useEffect(() => {
getIsp();
setFormValues(data && data.ispListEntry);
setUrlId(pathSplit);
}, [data, getIsp, pathSplit, formValues]);
const handleSubmit = () => {};
return (
<Fragment>
<div className='container p-4 parent-container'>
<ISPFormHeader />
<ISPFormHeaderPagename children='Update ISP Entry' />
<ISPForm
initialValues={data && data.ispListEntry}
enableReinitialize={true}
onSubmit={handleSubmit}
/>
<div className='editor-fields'>
<EditorComponent
defaultValue={notesFromAPI}
state={notesState}
onChange={setNotesState}
/>
</div>
<div className='editor-fields'>
<EditorComponent
placeholder='Enter Previous Notes Here'
state={previousState}
onChange={setPreviousState}
/>
</div>
</div>
</Fragment>
);
};
export default UpdateISPEntry;
and the Editor Component...
import React from 'react';
import ReactQuill from 'react-quill';
const modules = {
toolbar: [
[{ font: [] }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
['bold', 'italic', 'underline', 'strike'],
[{ color: [] }, { background: [] }],
[{ script: 'sub' }, { script: 'super' }],
['blockquote', 'code-block'],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ indent: '-1' }, { indent: '+1' }, { align: [] }],
['link', 'image', 'video'],
['clean']
]
};
const EditorComponent = ({ placeholder, state, onChange, defaultValue }) => {
return (
<ReactQuill
defaultValue={defaultValue}
modules={modules}
theme='snow'
placeholder={placeholder}
state={state}
onChange={onChange}
/>
);
};
export default EditorComponent;
I am try to use react-quill in my typescript nextjs project. Here I am finding typing and ref issue that I can't solve. Please help me.
Here is code example-
import React, { useState, useRef } from 'react';
import dynamic from 'next/dynamic';
import { Container } from "#mui/material";
const ReactQuill = dynamic(import('react-quill'), {
ssr: false,
loading: () => <p>Loading ...</p>,
})
import 'react-quill/dist/quill.snow.css';
const Editor = () => {
const [value, setValue] = useState('');
const quillRef = useRef<any>();
const imageHandler = async () => {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.click();
input.onchange = async () => {
var file: any = input && input.files ? input.files[0] : null;
var formData = new FormData();
formData.append("file", file);
let quillObj = quillRef.current.getEditor();
};
}
const modules = {
toolbar: {
container: [
[{ font: [] }, { 'size': [] }, { 'header': [1, 2, 3, 4, 5, 6] }],
['bold', 'italic', 'underline', 'strike'],
[{ 'color': [] }, { 'background': [] }],
[{ 'script': 'sub' }, { 'script': 'super' }],
[{ 'header': 1 }, { 'header': 2 }, 'blockquote', 'code-block'],
[
{ list: 'ordered' },
{ list: 'bullet' },
{ indent: '-1' },
{ indent: '+1' },
],
[{ 'direction': 'rtl' }, { 'align': [] }],
['link', 'image', 'clean'],
],
'handlers': {
image: imageHandler
}
}
}
return (
<Container maxWidth="xxxl" disableGutters>
<ReactQuill
ref={quillRef} // Here I am finding an issue.
value={value}
modules={modules}
onChange={setValue}
placeholder="Start typing!"
/>
</Container>
);
};
export default Editor;
Here is CodeSandBox-
https://codesandbox.io/s/still-hill-xkb1pj
Can any one give me a proper typescript solutions.
I am trying to develop a phaser3 application with React. I am just setting up the first canvas for the Phaser.Game. Here is my App.js from the create-react-app.
import "./App.css";
import Phaser, { Game } from "phaser";
import PhaserMatterCollisionPlugin from "phaser-matter-collision-plugin";
import { useCallback, useEffect, useState } from "react";
function App() {
const [game, setGame] = useState(null);
// Creating game inside a useEffect in order to ensure 1 instance is created
console.log("before use effect");
useEffect(() => {
console.log("Going into useEffect");
console.log(game);
if (game) {
console.log("game detected. stop creation");
return;
}
const phaserGame = new Phaser.Game({
width: 512,
height: 412,
backgroundColor: "#333333",
type: Phaser.AUTO,
parent: "survival-game",
scene: [],
scale: {
zoom: 2,
},
physics: {
default: "matter",
matter: {
debug: true,
gravity: { y: 0 },
},
},
plugins: {
scene: [
{
plugin: PhaserMatterCollisionPlugin,
key: "matterCollision",
mapping: "matterCollision",
},
],
},
});
setGame(true);
return;
}, [game]);
}
export default App;
I used useEffect() with useState in order to prevent multiple game instances, but for some reason I am still getting a duplicate canvas and can see that it is running through the useEffect multiple times. console.log of the react app
You should use a ref instead of state for the game object. Here's a small custom hook that sets up a Phaser.Game based on a given configuration:
function usePhaserGame(config) {
const phaserGameRef = React.useRef(null);
React.useEffect(() => {
if (phaserGameRef.current) {
return;
}
phaserGameRef.current = new Game(config);
return () => {
phaserGameRef.current.destroy(true);
phaserGameRef.current = null;
};
}, [] /* only run once; config ref elided on purpose */);
return phaserGameRef.current;
}
const config = {
width: 512,
height: 412,
backgroundColor: '#333333',
type: Phaser.AUTO,
parent: 'survival-game',
scene: [],
scale: {
zoom: 2,
},
physics: {
default: 'matter',
matter: {
debug: true,
gravity: {y: 0},
},
},
plugins: {
scene: [
{
plugin: PhaserMatterCollisionPlugin,
key: 'matterCollision',
mapping: 'matterCollision',
},
],
},
};
function App() {
const game = usePhaserGame(config);
}
Can someone help me to find out what is the issue in the code. I have created a custom image upload option but for some reason the variable "quillReact" is coming null when quillImageCallback function is invoked. I am using react-hooks. The image is uploaded properly when using API and proper response is also returned from the backend.
let quillReact: ReactQuill | null = null;
const updateIssueInfo = (value: string, delta: any, source: any, editor: any) => {
setIssueManagementInEdit({
...issueManagementInEdit,
description: value
});
};
const quillImageCallback = () => {
console.log(issueManagement);
const input = document.createElement("input");
input.setAttribute("type","file");
input.setAttribute("accept", "image/*");
input.click();
input.onchange = async () => {
const file: File | null = input.files ? input.files[0] : null;
if(file){
uploadImage(file).then(async (fileName: any) => {
const newFileName:string = await fileName.text();
console.log(quillReact);
let quill: any | null = quillReact?.getEditor();
console.log(quill);
const range : any | null = quill?.getSelection(true);
quill?.insertEmbed(range.index, 'image', `http://localhost:8080/uploads/${newFileName}`);
});
}
}
};
const module = React.useMemo(() => { return {
toolbar: {
container: [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
[{ 'header': 1 }, { 'header': 2 }], // custom button values
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
[{ 'script': 'sub'}, { 'script': 'super' }], // superscript/subscript
[{ 'indent': '-1'}, { 'indent': '+1' }], // outdent/indent
[{ 'direction': 'rtl' }], // text direction
[{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme
[{ 'font': [] }],
[{ 'align': [] }],
['clean', 'image'] // remove formatting button
],
handlers: {
image: quillImageCallback
}
},
clipboard: {
// toggle to add extra line breaks when pasting HTML:
matchVisual: false,
}
}},[]);
<ReactQuill
value={issueManagementInEdit.description ? issueManagementInEdit.description : ""}
onChange={updateIssueInfo}
modules={module}
ref={(el: ReactQuill) => {
quillReact = el;
} }
style={{height: "250px"}}
id="description"
key="description"
/>
Thank You.
I suggest you try useRef:
const quillRef = React.useRef(null);
<ReactQuill ... ref={quillRef} />
And then access the editor in your callback:
const quill = quillRef.current.getEditor();
I am trying to add dashed border to my bar charts. I am following this example here- https://jsfiddle.net/jt100/ghksq1xv/3/
I am not getting much luck I have followed the instruction very carefully and passing in the correct values but I am not adding the dashed border to my bar chart. Any help will be very much appreciated
This is what I have done
1) passed in 4 arguments: my chart instance, dataset, data and dash
```
this.dashedBorder(myLineChart, 0, 2, [15, 10]);
2) This is my function.
dashedBorder(chart, dataset, data, dash) {
chart.config.data.datasets[dataset]._meta[0].data[data].draw = function() {
chart.chart.ctx.setLineDash(dash);
Chart.elements.Rectangle.prototype.draw.apply(this,
arguments,
);
};
}
3) my whole react component. You can see what I have done here.
import React, { PureComponent } from "react";
import classes from "./YourLineGraph.module.css";
import Chart from "chart.js";
let myLineChart;
let myChartRef;
let ctx;
//--Chart Style Options--//
// Chart.defaults.global.defaultFontFamily = "'PT Sans', sans-serif";
Chart.defaults.global.defaultFontFamily = "'Cooper Hewitt'";
Chart.defaults.global.legend.display = false;
Chart.defaults.global.elements.line.tension = 0;
Chart.defaults.global.scaleLineColor = "tranparent";
Chart.defaults.global.tooltipenabled = false;
//--Chart Style Options--//
export default class YourLineGraph extends PureComponent {
chartRef = React.createRef();
componentDidMount() {
this.buildChart();
}
componentDidUpdate() {
this.buildChart();
}
buildChart = () => {
myChartRef = this.chartRef.current.getContext("2d");
ctx = document.getElementById("myChart").getContext("2d");
const { data, average, labels, attribute } = this.props;
if (typeof myLineChart !== "undefined") myLineChart.destroy();
myLineChart = new Chart(myChartRef, {
type: "bar",
data: {
//Bring in data
labels:
labels.length === data.length
? labels
: new Array(data.length).fill("Data"),
datasets: [
{
label: "Sales",
data: data,
borderColor: "#98B9AB",
borderWidth: 3,
borderStyle: "dash" //has no effect
}
]
},
options: {
plugins: {
datalabels: {
formatter: function(value, context) {
return attribute === "pounds" ? `£ ${value}` : value;
},
anchor: "end",
align: "end",
color: "#888"
}
},
scales: {
yAxes: [
{
gridLines: {
drawBorder: false,
display: false
},
ticks: {
display: false //this will remove only the label
}
}
],
xAxes: [
{
gridLines: {
drawBorder: false,
display: false
},
ticks: {
display: false //this will remove only the label
}
}
]
}
}
});
this.dashedBorder(myLineChart, 0, 2, [15, 10]);
};
dashedBorder(chart, dataset, data, dash) {
chart.config.data.datasets[dataset]._meta[0].data[data].draw = function() {
chart.chart.ctx.setLineDash(dash);
Chart.elements.Rectangle.prototype.draw.apply(this, arguments);
chart.chart.ctx.setLineDash([1, 0]);
};
}
render() {
return (
<div className={classes.graphContainer}>
<canvas id="myChart" ref={this.chartRef} />
</div>
);
}
}