How to move the cursor to the end of the line? - reactjs

I am using draft.js to develop a Rich text editor. I want the user to be able to keep typing once the Italic button is clicked. And inline styling should be applied until the user disable the italic button. Clicking on the button make the cursor to focus out of the editor. I created a ref and called the focus() function on the current ref and then called moveFocusToEnd on on edotorState. This does not work as expected. How do I achieve this behavior?
ReactJS
import React from 'react';
import { Editor, EditorState, RichUtils } from 'draft-js';
import { Button, Icon } from 'antd';
function MyEditor() {
const ref = React.useRef(null);
const [editorState, setEditorState] = React.useState(
EditorState.createEmpty()
);
const handleKeyCommand = command => {
const newState = RichUtils.handleKeyCommand(editorState, command);
if (newState) {
setEditorState(newState)
return "handled"
}
return "not-handled";
}
const onItalicClick = event => {
ref.current.focus()
EditorState.moveFocusToEnd(editorState)
setEditorState(RichUtils.toggleInlineStyle(editorState, 'ITALIC'))
}
const onUnderLinkClick = event => {
event.preventDefault()
setEditorState(RichUtils.toggleInlineStyle(editorState, "UNDERLINE"))
}
const onBoldClick = event => {
event.preventDefault()
console.log(event)
setEditorState(RichUtils.toggleInlineStyle(editorState, "BOLD"))
}
return <div>
<div>
<Button
onClick={onItalicClick}
>
<Icon type="italic" />
</Button>
<Button
onClick={onUnderLinkClick}
>
<Icon type="underline" />
</Button>
<Button
onClick={onBoldClick}
>
<Icon type="bold" />
</Button>
</div>
<Editor
editorState={editorState}
onChange={editorState => setEditorState(editorState)}
handleKeyCommand={handleKeyCommand}
ref={ref}
/>
</div>;
}
export default MyEditor;
SCSS
.wrapper {
border: 1px solid #e2e2e2;
padding: 10px;
margin-bottom: 20px;
}

selectionState = this.state.editorState.getSelection()
selectionState=selectionState.merge({'forceKey':xxxx, focusOffset:5})
here you can set focusOffset to be the text length of that block.

Related

Cannot assign to read only property 'backgroundColor' of object '#<Object>'

import React, { useState } from "react";
function App() {
const [headingText, setHeadingText] = useState("Hello");
const changeColor = { backgroundColor: "" };
function handleClick() {
setHeadingText("Submitted");
}
function handleMouseOver() {
changeColor.backgroundColor = "black";
}
function handleMouseOut() {
changeColor.backgroundColor = "white";
}
return (
<div className="container">
<h1>{headingText}</h1>
<input type="text" placeholder="What's your name?" />
<button
style={changeColor}
onClick={handleClick}
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
>
Submit
</button>
</div>
);
}
export default App;
ERROR:Cannot assign to read only property 'backgroundColor' of object '#'
I was trying to create a kind of form, in which the submit button should change to black on hover and to white on getting out.Can anyone help it out.
I tried your code but I am not getting the error you mentioned.
ERROR: Cannot assign to read-only property 'backgroundColor' of object '#'`
Also changing the constant changeColor will have no impact on the button as it will not cause the re-render. You need to use it as state variables.
const [changeColor, setChangeColor] = useState({ backgroundColor: "" });
function handleMouseOver() {
setChangeColor({
backgroundColor: "black"
});
}
function handleMouseOut() {
setChangeColor({
backgroundColor: "white"
});
}
But this can also be achieved using :hover selector as follows
.button_submit:hover {
background-color: black;
}
for the button
<button
className="button_submit"
onClick={handleClick}
>
Submit
</button>

A resizable `antd` Drawer?

I would like to provide a way to make an antd Drawer resizable ?
I read a popular answer specifically for material-ui/Drawer but I am looking to do something very similar with antd.
Does anyone have a similar antd example - or have a better idea how to handle info getting chopped off at side of the drawer.
You can extend the width of Drawer by specifying it on the width props. If you don't want to extend it but you want the content to be still fit, you can set the width on bodyStyle prop and use overflow: "auto":
<Drawer
title="Basic Drawer"
placement="right"
closable={false}
visible={isDrawerVisible}
bodyStyle={{
width: 400,
overflow: "auto"
}}
onClose={toggleDrawerVisible}
>
I also made a resizable drawer based on the link that you provide in antd version (react hooks version answer).
ResizableDrawer.jsx
import React, { useState, useEffect } from "react";
import { Drawer } from "antd";
let isResizing = null;
const ResizableDrawer = ({ children, ...props }) => {
const [drawerWidth, setDrawerWidth] = useState(undefined);
const cbHandleMouseMove = React.useCallback(handleMousemove, []);
const cbHandleMouseUp = React.useCallback(handleMouseup, []);
useEffect(() => {
setDrawerWidth(props.width);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.visible]);
function handleMouseup(e) {
if (!isResizing) {
return;
}
isResizing = false;
document.removeEventListener("mousemove", cbHandleMouseMove);
document.removeEventListener("mouseup", cbHandleMouseUp);
}
function handleMousedown(e) {
e.stopPropagation();
e.preventDefault();
// we will only add listeners when needed, and remove them afterward
document.addEventListener("mousemove", cbHandleMouseMove);
document.addEventListener("mouseup", cbHandleMouseUp);
isResizing = true;
}
function handleMousemove(e) {
let offsetRight =
document.body.offsetWidth - (e.clientX - document.body.offsetLeft);
let minWidth = 256;
let maxWidth = 600;
if (offsetRight > minWidth && offsetRight < maxWidth) {
setDrawerWidth(offsetRight);
}
}
return (
<Drawer {...props} width={drawerWidth}>
<div className="sidebar-dragger" onMouseDown={handleMousedown} />
{children}
</Drawer>
);
};
export default ResizableDrawer;
and to use it:
import ResizableDrawer from "./ResizableDrawer";
<ResizableDrawer
title="Resizable Drawer"
placement="right"
closable={false}
visible={isResizableDrawerVisible}
onClose={toggleResizableDrawerVisible}
>
...
</ResizableDrawer>
See working demo here:
Have two states for tracking the width of the drawer and whether or not the drawer is being resized (isResizing).
Add two event listeners on the global document where it will listen for mousemove and mouseup. The mousemove event will resize the drawer, only if isResizing is true. And the mouseup event will set isResizing to false.
Add a div in your drawer that acts as the draggable border for making the drawer resizable. This div will listen for a mousedown event, which will set the state of isResizing to true.
Here's the code that has been improved upon from the basic drawer demo from antd's website.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Drawer, Button } from "antd";
const App = () => {
const [visible, setVisible] = useState(false);
const [isResizing, setIsResizing] = useState(false);
const [width, setWidth] = useState(256);
const showDrawer = () => {
setVisible(true);
};
const onClose = () => {
setVisible(false);
};
const onMouseDown = e => {
setIsResizing(true);
};
const onMouseUp = e => {
setIsResizing(false);
};
const onMouseMove = e => {
if (isResizing) {
let offsetRight =
document.body.offsetWidth - (e.clientX - document.body.offsetLeft);
const minWidth = 50;
const maxWidth = 600;
if (offsetRight > minWidth && offsetRight < maxWidth) {
setWidth(offsetRight);
}
}
};
useEffect(() => {
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
return () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
});
return (
<>
<Button type="primary" onClick={showDrawer}>
Open
</Button>
<Drawer
title="Basic Drawer"
placement="right"
closable={false}
onClose={onClose}
visible={visible}
width={width}
>
<div
style={{
position: "absolute",
width: "5px",
padding: "4px 0 0",
top: 0,
left: 0,
bottom: 0,
zIndex: 100,
cursor: "ew-resize",
backgroundColor: "#f4f7f9"
}}
onMouseDown={onMouseDown}
/>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Drawer>
</>
);
};
ReactDOM.render(<App />, document.getElementById("container"));
And here's the demo of the code:
DEMO

How to get new value from react ace editor on button click

I'm trying to alert the final value of the ace editor when a button is clicked. I know there's an onChange event for Ace Editor, just not sure on how to get that value into my handleClick function.
This is my current code:
import ReactAce from "react-ace";
import React from "react";
import "ace-builds/src-noconflict/mode-python";
import "ace-builds/src-noconflict/theme-twilight";
function onChange(newValue) {
console.log("change", newValue);
}
function handleClick() {
alert();
}
function CodeEditor(props) {
return (
<>
<ReactAce
value={props.value}
mode="python"
theme="twilight"
showPrintMargin={false}
setReadOnly={false}
setValue={props.value}
fontSize={18}
style={{
height: "620px",
width: "100%",
}}
onChange={onChange}
/>
<button onClick={handleClick}>Run Code</button>
</>
);
}
export default CodeEditor;
You can use a useState hook to manage the state of the text.
function CodeEditor(props) {
// save the state here
const [ text, setText ] = useState('')
const handleClick = () => {
alert(text)
}
return (
<>
<ReactAce
value={props.value}
mode="python"
theme="twilight"
showPrintMargin={false}
setReadOnly={false}
setValue={props.value}
fontSize={18}
style={{
height: "620px",
width: "100%",
}}
// set the state when the value changes
onChange={(e) => setText(e.target.value)}
/>
<button onClick={() => handleClick()}>Run Code</button>
</>

How to display a dialog when a button is clicked using react and typescript?

i want to display a dialog to the bottom right corner of the page on clickin a button using react and typescript.
There is a button named "Add" in ListComponent. when clicking that button dialog which is the DialogComponent should be rendered.
Below is how the ListComponent and DialogComponent looks
function ListComponent() {
const onAddClicked = () => {
//what to be done
}
return (
<button onClick={onAddClicked}> Add</button>
);
}
function DialogComponent() {
return (
<Wrapper> Dialog </Wrapper>
)
}
Now i cannot call DailogComponent within ListComponent as it would push the layout of the page.
So i want to call this DailogComponent within MainComponent which is something like below
function Main () {
return (
<ListComponent>
//some props
</ListComponent>
)
}
I am new to using react with typescript. How can i do this. could someone provide some insights into this. thanks.
You will likely want to use fixed positioning (and possibly some increased z-index, if necessary) to display a dialog box at the bottom right of the screen. This will "break" it out of being rendered "inline" with your content.
At a minimum you should utilize the following CSS rules
position: fixed;
bottom: <M>; // M units from the bottom
right: <N>; // N units from the right
The linked codesandbox sets this Dialog class style and component
CSS
.dialog {
position: fixed;
bottom: 1rem;
right: 1rem;
padding: 1rem;
border-radius: 5px;
background-color: lightblue;
}
JSX
const Dialog = ({ showDialog, text }) =>
showDialog ? <div className="dialog">{text}</div> : null;
How you toggle the display state of the dialog is up to you.
EDIT: Full code with a demo ListComponent
const Dialog = ({ showDialog, text }) =>
showDialog ? <div className="dialog">{text}</div> : null;
const ListComponent = ({ data, toggleDialog }) =>
data.map((el, i) => (
<div key={i}>
<button type="button" onClick={toggleDialog}>
{el}
</button>
</div>
));
export default function App() {
const [showDialog, setShowDialog] = useState();
const toggleDialog = () => setShowDialog(s => !s);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button type="button" onClick={toggleDialog}>
Toggle Dialog in Main
</button>
<h2>List Component</h2>
<ListComponent data={["A", "B", "C"]} toggleDialog={toggleDialog} />
<Dialog showDialog={showDialog} text="I'm a dialog" />
</div>
);
}
This is essentially applying Lifted State. State and mutators are lifted to a common ancestor of components interested in either displaying the state, or updating. State and state updaters are passed as props to children.
You need to have a callback in order to display the dialog box from Main function.
Below is an example:
import React, { useState } from "react";
import { render } from "react-dom";
import Modal from "react-modal";
const Main = () => {
const [isOpen, setIsOpen] = useState(false);
const handleAddClick = () => {
setIsOpen(true);
};
return (
<React.Fragment>
<ListComponent onAddClickedProps={handleAddClick} />
<DialogComponent isOpen={isOpen} />
</React.Fragment>
);
};
const ListComponent = ({ onAddClickedProps }) => {
const onAddClicked = () => {
onAddClickedProps();
};
return <button onClick={onAddClicked}> Add</button>;
};
const DialogComponent = ({ isOpen }) => {
return (
<Modal isOpen={isOpen}>
<div>I am a modal</div>
</Modal>
);
};
render(<Main />, document.getElementById("root"));
React-modal-example-on-sandbox

How to prevent Link from messing up my radio buttons behavior?

I am trying to build a menu, that shows the current page in bold.
For that I am using radio buttons so that each item on the menu is a label for that radio button, and in my css I make an active item bold.
My problem is, that because the label is wrapped in a Link element, when an item is clicked nothing really changes. It navigates properly but the radio button sate doesn't change. Maybe everything just re renders ignoring my action?
It works just fine without the link element. Why is that? And what can I do to make it work?
This is the code for my menu component:
import "./styles.scss";
import React from "react";
import { Link } from "react-router-dom";
const Menu = () => {
const turnToLowerCaseWithHyphen = string => {
return (string[0].toLowerCase() + string.slice(1)).replace(" ", "-");
};
const renderMenuItems = array => {
return array.map(item => {
const smallHyphenedItem = turnToLowerCaseWithHyphen(item);
return (
<div className="flex-group" key={smallHyphenedItem}>
<input
className="menu-item__radio"
id={smallHyphenedItem}
type="radio"
name="menu-items"
onChange={() => console.log(smallHyphenedItem)}
/>
<Link
to={"/" + smallHyphenedItem}
className="menu-item"
key={smallHyphenedItem}
>
<label htmlFor={smallHyphenedItem} className="menu-item__label">
{item}
</label>
</Link>
</div>
);
});
};
return (
<div className="menu">
{renderMenuItems(["Feed", "Search", "Contact us"])}
</div>
);
};
export default Menu;
EDIT: I've tried to use a state in the menu component but that doens't help either:
const Menu = () => {
const [currentPage, setCurrentPage] = useState(null);
const turnToLowerCaseWithHyphen = string => {
return (string[0].toLowerCase() + string.slice(1)).replace(" ", "-");
};
const renderMenuItems = array => {
return array.map(item => {
const smallHyphenedItem = turnToLowerCaseWithHyphen(item);
return (
<div className="flex-group" key={smallHyphenedItem}>
<input
className="menu-item__radio"
id={smallHyphenedItem}
type="radio"
name="menu-items"
checked={currentPage === smallHyphenedItem}
onChange={() => setCurrentPage(smallHyphenedItem)}
/>
<label htmlFor={smallHyphenedItem} className="menu-item__label">
<Link
to={"/" + smallHyphenedItem}
className="menu-item"
key={smallHyphenedItem}
>
{item}
</Link>
</label>
</div>
);
});
};
return (
<div className="menu">
{renderMenuItems(["Feed", "Search", "Contact us"])}
</div>
);
};
export default Menu;
This was a bit more of a headach than I've expected, but I got it working.
Like Sasha suggested, I needed to store the choice in a state using redux to have it persist between pages.
But that isn't enough. using Link didn't allow for the action to be executed before navigating (to my understanding).
What I had to do was instead of using Link, to just navigate to the page I wanted using the history.push() command.
This is my final working code:
import "./styles.scss";
import React from "react";
import { connect } from "react-redux";
import { Link, useHistory } from "react-router-dom";
import history from "../../../history";
import { setCurrentPage } from "../../../actions";
const Menu = ({ setCurrentPage, page }) => {
const myHistory = useHistory(history);
console.log(page);
const turnToLowerCaseWithHyphen = string => {
return (string[0].toLowerCase() + string.slice(1)).replace(" ", "-");
};
const handleChange = (page) => {
setCurrentPage(page);
myHistory.push(`/${page}`)
};
const renderMenuItems = array => {
return array.map(item => {
const smallHyphenedItem = turnToLowerCaseWithHyphen(item);
return (
<div className="flex-group" key={smallHyphenedItem}>
<input
className="menu-item__radio"
id={smallHyphenedItem}
type="radio"
name="menu-items"
checked={page === smallHyphenedItem}
onChange={() => handleChange(smallHyphenedItem)}
/>
<label htmlFor={smallHyphenedItem} className="menu-item__label">
{/* <Link
to={"/" + smallHyphenedItem}
className="menu-item"
key={smallHyphenedItem}
> */}
{item}
{/* </Link> */}
</label>
</div>
);
});
};
return (
<div className="menu">
{renderMenuItems(["Feed", "Search", "Contact us"])}
</div>
);
};
const mapStateToProps = state => {
return {
page: state.page
};
};
export default connect(mapStateToProps, { setCurrentPage })(Menu);
And this is my CSS:
.menu {
display: grid;
grid-template-columns: repeat(4, max-content);
grid-gap: 3rem;
&-item {
&__label {
font-family: var(--font-main);
font-size: 1.6rem;
transition: all 0.2s;
text-decoration: none;
color: var(--color-grey-medium);
width: max-content;
&:hover {
color: var(--color-main);
}
}
&__radio {
visibility: hidden;
opacity: 0;
&:checked + .menu-item__label {
color: var(--color-main);
}
}
}
}

Resources