React-Quill non-editable embed - javascript

Using react-quill. I want to add a non-editable block of text, I am able to create the blot, but if I try to add a contenteditable=false attribute to it, it does not work. My code is as follows
import ReactQuill from 'react-quill';
import './App.scss';
import 'react-quill/dist/quill.snow.css';
import { useState, useRef } from 'react';
const Quill = ReactQuill.Quill;
const BlockEmbed = Quill.import('blots/embed');
class Mention extends BlockEmbed {
static create(value) {
let node = super.create(value);
node.innerText = value;
// node.contenteditable = false;
node.setAttribute("contenteditable", false);
return node;
}
static value(node) {
return node.childNodes[0].textContent;
}
}
Mention.blotName = 'label';
Mention.tagName = 'SPAN';
Mention.className = 'ql-label';
Quill.register(Mention);
function App() {
const [value, setValue] = useState('');
const thisEditor = useRef(null);
const inserMention = (thisEditor) => {
const editor = thisEditor.getEditor();
let range = editor.getSelection();
let position = range ? range.index : 0;
editor.insertEmbed(position, 'label');
}
return (
<div className="container bg-crow-green bg-gradient px-0">
<div className='mt-4 border rounded'>
<ReactQuill ref={thisEditor} theme='snow' value={value} onChange={setValue} />
<button type="button" className="btn mt-4 btn-danger w-25" onClick={() => inserMention(thisEditor.current)}>Insert</button>
</div>
</div >
);
}
export default App;
Clicking the button Insert, creates a new Embed and adds it to the editor, but it is editable which i do not want. Another problem is I want to reference the newly added embed and later on change its value too, I can do this using Parment.find() and later use format but I cannot figure out how to do this in react.

Try using camcelCase property.
node.setAttribute("contentEditable", false);

Related

How to pass multiple classNames as props with css modules

I can't seem to understand how to pass a string with multiple classNames as a prop using css modules? I have different classes i use everywhere for the button, and so i have on class for when it's active, and another when it's inactive. I also have different color schemes i pass.
Button:
import styles from "./Button.module.css";
const Button = (props) => {
return (
<button className={`${styles.standardButton} ${styles[props.className]}`}>
<h1>{props.text}</h1>
</button>
);
};
export default Button;
And here is the page which uses the button. The button function is at the bottom, and some input fields needs to be filled out in order for it to be "active".
import React, { useState } from "react";
import Input from "../input/Input";
import Select from "../input/Select";
import Button from "../input/Button";
const CreateJobPage1 = (props) => {
const [enteredName, setEnteredName] = useState("");
const [enteredCompany, setEnteredCompany] = useState("");
const [enteredLocation, setEnteredLocation] = useState("");
const [enteredText, setEnteredText] = useState("");
const [projectType, setProjectType] = useState('DEFAULT');
const projectTypes = ["shortfilm", "fiction", "commercial", "arthouse", "animation"];
const nameChangeHandler = (props) => {
setEnteredName(props);
};
const companyChangeHandler = (props) => {
setEnteredCompany(props);
};
const locationChangeHandler = (props) => {
setEnteredLocation(props);
};
const textChangeHandler = (props) => {
setEnteredText(props);
};
const projectTypeHandler = (props) => {
setProjectType(props);
};
return (
<div>
<Input
placeholder="What's the project name?"
enteredValue={enteredName}
onChange={nameChangeHandler}
/>
<Input
placeholder="What's the production company?"
enteredValue={enteredCompany}
onChange={companyChangeHandler}
/>
<Input
placeholder="Where's the project located?"
enteredValue={enteredLocation}
onChange={locationChangeHandler}
/>
<Select
placeholder="Choose project type"
options={projectTypes}
value={projectType}
onChangeOption={projectTypeHandler}
/>
<Input
placeholder="What's the project about?"
enteredValue={enteredText}
onChange={textChangeHandler}
formulaType="textarea"
/>
<Button
className={enteredName === "" || enteredCompany === "" || enteredLocation === "" || projectType === "DEFAULT" ? ["isRed", "formButton", "inActive"] : ["isRed", "formButton"]}
text={"Add Functions"}
/>
</div>
);
};
export default CreateJobPage1;
const classNameTest = ['isRed', 'fontColor']
<div className={`${styles.cardWrapper} ${classNameTest.map((item) => {
return styles[item] + ' '
})}`}
>
As I mentioned above in the comment, all the styles in xxx.module.scss is mean to prevent different style files have duplicate className and they will effect each other globally. when you do styles.isRed the REAL class name will likely to be src-pages-YourFileName-module__isRed--SOMEUNIQUECODE
when you try to pass them as props, u still need to access them by import styles
when you do a map return u need to have space between each return so at the end html will know they are different class
I still think is better to pass the status like isActive to control the component style. otherwise this component is not really fit the idea of a component

Accessing Child Component in GrandParent component

I am building a Search Algorithm Visualizer website.
The order of components is App->Row->Box
Each box component has its own unique id (rowNumber-colNumber) and has its classname set
as "Unvisited"
I have an onclick event in App Component which runs BFS
To visualize Bfs,I have to access and change some specific (based on their id) Box Component's classname to "Visited"
How can i achieve it?
These are my components
//APP COMPONENT
import React from "react"
import Row from "./Row"
export const Start = {row:14,col: 18},End = {row:14,col: 35}
export default function App(){
const [grid,setGrid] = React.useState([])
React.useEffect(() => {
for(let i = 1;i<29;i++){
setGrid(prevArray =>prevArray.concat(<Row key = {i} row = {i}/>))
}
},[])
return (
<>
<div className = "Navbar">
<a href ="#" onClick = {Bfs}>Breadth First Search</a>
</div>
{grid}
</>
)
}
//ROW COMPONENT
import React from "react"
import Box from "./Box"
import {Start,End} from "./App"
export default function Row(props){
const row = [];
for(let colNumber = 1;colNumber<54;colNumber++){
row.push(<Box
key = {colNumber}
row = {props.row}
col = {colNumber}
isStart = {props.row == Start.row && colNumber == Start.col}
isEnd = {props.row == End.row && colNumber == End.col}
/>)
}
return (
<div className ="row">
{row}
</div>
)
}
//BOX COMPONENT
import React from "react";
export default function Box({ row, col, isStart, isEnd }) {
return (
<div className="Unvisited" id={`${row}-${col}`}>
{isStart && <img src="./images/start.png"></img>}
{isEnd && <img src="./images/target.png"></img>}
</div>
)
}
In App component;
const grid = []
since you are using grid as a variable, react doesn't care if the grid changes;
you have to use useState hook
const [grid, setGrid] = useState([])
and usign setGrid you have to update the value of grid.
React will automatically rerender the component once grid is changed using setGrid.
Whenever you need to update some data on page you need to use state in react

React way of grabbing a button element and using disable property

I have the following code where a user can select a file and hit upload and the Choose button is disabled.
Code Sanbox link is here:
import "primeicons/primeicons.css";
import "primereact/resources/themes/lara-light-indigo/theme.css";
import "primereact/resources/primereact.css";
import "primeflex/primeflex.css";
import "../../index.css";
import ReactDOM from "react-dom";
import React, { useRef, useState } from "react";
import { FileUpload } from "primereact/fileupload";
export const FileUploadDemo = () => {
const toast = useRef(null);
const [disableButton, setDisableButton] = useState(false);
const onUpload = () => {
toast.current.show({
severity: "info",
summary: "Success",
detail: "File Uploaded"
});
};
const onTemplateAdvancedSelect = (e) => {
console.log("Printing onTemplateAdvancedSelect ");
console.log(e);
let inputFileType = document.querySelector("input[type=file]");
//setDisableButton(true);
inputFileType.classList.add("toDisableOnSelect");
inputFileType.disabled = true;
let htmlCollection = document.getElementsByClassName(
"p-button p-component p-button-icon-only"
);
console.log("Printing htmlCollection");
console.log(htmlCollection.length);
console.log(htmlCollection);
// htmlCollection.addEventListener("click", function () {
// inputFileType.disabled = false;
// });
//console.log(htmlCollection.item(19));
};
return (
<div>
<div className="card">
<h5>Advanced</h5>
<FileUpload
multiple={false}
name="demo[]"
url="https://primefaces.org/primereact/showcase/upload.php"
onUpload={onUpload}
id={"myId"}
accept="image/*"
maxFileSize={1000000}
onSelect={onTemplateAdvancedSelect}
disabled={disableButton}
emptyTemplate={
<p className="p-m-0">Drag and drop files to here to upload.</p>
}
/>
</div>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<FileUploadDemo />, rootElement);
So Inside onTemplateAdvancedSelect function, I want to set inputFileType.disabled to false once user hits the cross icon as shown below:
I don't want to use getElementByClassName just like I have attempted to use in my code of above function. What would be a better way to achieve my goal?
Primereact version I'm using : 4.2.2
One solution is customizing the row item of the selected file.
so the itemTemplate prop is exactly for that goal.
export const FileUploadDemo = () => {
const toast = useRef(null);
const fileUploadRef = useRef(null) // reference to uploader node
const [disableButton, setDisableButton] = useState(false);
const onTemplateRemove = (file, callback) => {
// setTotalSize(totalSize - file.size);
const domInput = fileUploadRef.current.fileInput;
domInput.disabled = false;
callback();
}
const onTemplateAdvancedSelect = () => {
const domInput = fileUploadRef.current.fileInput; // pure dome element
domInput.disabled = true;
}
const itemTemplate = (file, props) => {
return (
<>
<div>
<img alt={file.name} role="presentation" src={file.src} width="50" />
</div>
<div class="p-fileupload-filename">{file.name}</div>
<div>{file.size}</div>
<div>
{ /* here you have access to that button */}
<button
type="button"
class="p-button p-component p-button-icon-only"
onClick={() => onTemplateRemove(file, props.onRemove)}>
<span class="p-button-icon p-c pi pi-times"></span>
<span class="p-button-label p-c"> </span>
</button>
</div>
</>
)
}
return (
<div>
<div className="card">
<h5>Advanced</h5>
<FileUpload
ref={fileUploadRef} // pass a reference for `input` manipulation
multiple={false}
name="demo[]"
url="https://primefaces.org/primereact/showcase/upload.php"
onUpload={onUpload}
id={"myId"}
accept="image/*"
maxFileSize={1000000}
/* here we should pass the customized template as prop */
itemTemplate={itemTemplate}
onSelect={onTemplateAdvancedSelect}
disabled={disableButton}
emptyTemplate={
<p className="p-m-0">Drag and drop files to here to upload.</p>
}
/>
</div>
</div>
);
Please use useRef hook provided by react. Example below:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
I'm trying to understand your use case. Do you want to remove the image from the upload stack by clicking the x button?
I think you should rather use the built-in API to trigger functionality. If that doesn't help you can extend the library.
You don't need to do a getElementsByClassName. You can use the onRemove event to handle the file remove event.
<FileUpload
multiple={false}
name="demo[]"
url="https://primefaces.org/primereact/showcase/upload.php"
onUpload={onUpload}
id={"myId"}
accept="image/*"
maxFileSize={1000000}
onSelect={onTemplateAdvancedSelect}
disabled={disableButton}
emptyTemplate={
<p className="p-m-0">Drag and drop files to here to upload.</p>
}
onRemove={(e, file) => setDisableButton(false)}
/>

How can I append a React component to an html element i?

I am using the modal library winbox. It works well if use simple html and javascript. But I can't append a React node to it.
The modal has a html parameter, that accept an html string such as div.innerHTML = <div>hello</div> . The code source is: this.body.innerHTML = html;.
So adding a classic React element makes the modal crash. The only solution I found is to use react-dom's renderToString method: html: renderToString(children). But the component is not dynamic anymore (no state update, etc.).
I also tried to surround React.renderDOM by a div inside the modal and attach the component to it, as the app is attached to index.js's #root div.
html: <div id="modal">
{render(
<div children={content} />,
document.querySelector("#modal")
)},
</div>
My question is thus: how to pass a dynamic React component to the body of this modal?
Here is my code:
import React from "react";
import useModal from "./useModal";
const Counter = () => {
const [count, setCount] = useState(1);
return (
<div>
The count is {count}
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
};
export default function App() {
const [open, setOpen] = useState(false);
useModal(open, setOpen, <Counter />);
return (
<div id="#hi">
<button onClick={() => setOpen(!open)}>toggle</button>
</div>
);
}
// useModal
import WinBox from "winbox/src/js/winbox.js";
import "winbox/dist/css/winbox.min.css";
import React from "react";
import { render } from "react-dom";
export default function useModal(open, onToggle, content) {
const modal = open
? new WinBox({
title: "Hello",
max: true,
html: content, // INSERT DYNAMIC COMPONENT HERE
onclose: () => onToggle(false)
})
: null;
return modal;
}
Thank you!
You can use winbox-react package from npm. winbox-react npm link Example code is given below. Youcan use normal jsx as children of the WinboxReact component.
const Hero = () => {
const [show, setShow] = useState(false)
const clickHandeler = () => {
setShow(!show)
}
return (
<div className='text-center'>
{show && (
<WinboxReact
onClose={clickHandeler}
background='linear-gradient(90deg, rgba(49,36,239,1) 0%, rgba(67,0,168,1) 100%)'
><h1>Hello</h1></WinboxReact>
)}
<button onClick={clickHandeler} className='btn btn-custom btn-lg mt-4'>
Show Example
</button>
</div>
)
}
export default Hero

How do I add the ability to edit text within a react component?

So here's the user function I'm trying to create:
1.) User double clicks on text
2.) Text turns into input field where user can edit text
3.) User hits enter, and upon submission, text is updated to be edited text.
Basically, it's just an edit function where the user can change certain blocks of text.
So here's my problem - I can turn the text into an input field upon a double click, but how do I get the edited text submitted and rendered?
My parent component, App.js, stores the function to update the App state (updateHandler). The updated information needs to be passed from the Tasks.jsx component, which is where the text input is being handled. I should also point out that some props are being sent to Tasks via TaskList. Code as follows:
App.js
import React, {useState} from 'react';
import Header from './Header'
import Card from './Card'
import cardData from './cardData'
import Dates from './Dates'
import Tasks from './Tasks'
import Footer from './Footer'
import TaskList from './TaskList'
const jobItems= [
{
id:8,
chore: 'wash dishes'
},
{
id:9,
chore: 'do laundry'
},
{
id:10,
chore: 'clean bathroom'
}
]
function App() {
const [listOfTasks, setTasks] = useState(jobItems)
const updateHandler = (task) => {
setTasks(listOfTasks.map(item => {
if(item.id === task.id) {
return {
...item,
chore: task.chore
}
} else {
return task
}
}))
}
const cardComponents = cardData.map(card => {
return <Card key = {card.id} name = {card.name}/>
})
return (
<div>
<Header/>
<Dates/>
<div className = 'card-container'>
{cardComponents}
</div>
<TaskList jobItems = {listOfTasks} setTasks = {setTasks} updateHandler = {updateHandler}/>
<div>
<Footer/>
</div>
</div>
)
}
export default App;
Tasks.jsx
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
TaskList.jsx
import React from 'react'
import Tasks from './Tasks'
function TaskList (props) {
const settingTasks = props.setTasks //might need 'this'
return (
<div>
{
props.jobItems.map(item => {
return <Tasks key = {item.id} item = {item} setTasks = {settingTasks} jobItems ={props.jobItems} updateHandler = {props.updateHandler}/>
})
}
</div>
)
}
export default TaskList
You forgot onChange handler on input element to set item's chore value.
Tasks.jsx must be like below
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
const handleInputChange = (e)=>{
// console.log( e.target.value );
// your awesome stuffs goes here
}
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' onChange={handleInputChange} defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
So, first of all, I would encourage you not to switch between input fields and divs but rather to use a contenteditable div. Then you just use the onInput attribute to call a setState function, like this:
function Tasks ({item}) {
return(
<div className = 'tasks-container'>
<div contenteditable="true" onInput={e => editTask(item.id, e.currentTarget.textContent)} >
{item.chore}
</div>
</div>
)
}
Then, in the parent component, you can define editTask to be a function that find an item by its id and replaces it with the new content (in a copy of the original tasks array, not the original array itself.
Additionally, you should avoid renaming the variable between components. (listOfTasks -> jobItems). This adds needless overhead, and you'll inevitably get confused at some point which variable is connected to which. Instead say, <MyComponent jobItems={jobItems} > or if you want to allow for greater abstraction <MyComponent items={jobItems} > and then you can reuse the component for listable items other than jobs.
See sandbox for working example:
https://codesandbox.io/s/practical-lewin-sxoys?file=/src/App.js
Your Task component needs a keyPress handler to set isEditing to false when enter is pressed:
const handleKeyPress = (e) => {
if (e.key === "Enter") {
setIsEditing(false);
}
};
Your updateHandler should also be passed to the input's onChange attribute, and instead of defaultValue, use value. It also needs to be reconfigured to take in the onChange event, and you can map tasks with an index to find them in state:
const updateHandler = (e, index) => {
const value = e.target.value;
setTasks(state => [
...state.slice(0, index),
{ ...state[index], chore: value },
...state.slice(index + 1)
]);
};
Finally, TaskList seems like an unnecessary middleman since all the functionality is between App and Task; you can just render the tasks directly into a div with a className of your choosing.
react-edit-text is a package I created which does exactly what you described.
It provides a lightweight editable text component in React.
A live demo is also available.

Categories

Resources