Reputation: 95
I want to adjust my textarea height dynamically with Refs and pass it to the state but it don't work correctly.
I created a codesandbox to help you to understand what exactly I want.
https://codesandbox.io/s/ol5277rr25
Upvotes: 5
Views: 20085
Reputation: 1
interface IProps {
value: string | number;
onChange: (value: string) => void;
cellId: string;
onKeyDownForNumberInput?: (e: KeyboardEvent) => void;
handleBlur: () => void;
isReadOnly: boolean;
}
const TableCellEditable = forwardRef(
(
{
value,
onChange,
isReadOnly,
cellId,
onKeyDownForNumberInput,
handleBlur,
}: IProps,
ref: any
): JSX.Element => {
const [position, setPosition] = useState<number>(0);
// Вариант из stackOverflow
const getCursorPosition = (parent: Node): void => {
let selection = document?.getSelection();
let range = new Range();
range?.setStart(parent, 0);
if (
selection?.anchorNode !== null &&
selection?.anchorNode !== undefined &&
selection?.anchorOffset !== null &&
selection?.anchorOffset !== undefined
) {
range.setEnd(selection?.anchorNode, selection?.anchorOffset);
const position = range?.toString()?.length;
setPosition(position);
}
};
// Вариант из stackOverflow
const setCursorPosition = (parent: Node, position: number): void => {
let child = parent?.firstChild;
if (child) {
while (position > 0) {
let length = child?.textContent?.length;
if (length !== undefined && position > length) {
position -= length;
child = child?.nextSibling as any;
} else {
if (child?.nodeType === 3)
return document?.getSelection()?.collapse(child, position);
child = child?.firstChild as any;
}
}
}
};
const handleChange = (evt: React.ChangeEvent<any>) => {
let el = document?.getElementById(cellId);
if (el) {
getCursorPosition(el);
}
onChange(evt.target.textContent);
};
const onBlur = () => {
handleBlur();
};
useLayoutEffect(() => {
let el = document?.getElementById(cellId);
if (el) {
setCursorPosition(el, position);
}
}, [value, cellId, position]);
return (
<table className={cx("table")}>
<tbody>
<tr className={cx("table__row")}>
{!isReadOnly ? (
<td
suppressContentEditableWarning={true}
contentEditable={true}
onInput={handleChange}
ref={ref as any}
id={cellId}
className={cx("table__cell")}
onKeyDown={onKeyDownForNumberInput as any}
onBlur={onBlur}
>
{value}
</td>
) : (
<td className={cx("readonly")}>{value}</td>
)}
</tr>
</tbody>
</table>
);
I was faced with inserting a text area into the <td>
cell, and it increased as I entered the text. I tried it in different ways. As a result, I made the cell itself
<td suppressContentEditableWarning={true}
contentEditable={true}>
. The React showed an error without the suppressContentEditableWarning={true} property. But it works in Chrome and Firefox as needed. Also, the onChange event does not work. Used the onInput={handleInputChange}
event. And since there is no value in <td>
, I took event.target.textContent
. I don't know how safe and correct this is. Therefore, you do all this under your responsibility.
Update. I'm sorry, I'm new to posting here. In general, the method I described only works a little. Since react works unpredictably with contentEditable, the cursor is lost when entering text!!! (You can make the component memoized with a ban on updating, but this leads to unpredictable work) Now it seems that I have solved the problem with the help of the library https://www.npmjs.com/package/react-contenteditable . It allows you to work in react applications with tags with the contentEditable property. So what I did: instead of a cell, I inserted a component provided by the library into my table, and this cell became an editable cell that automatically increases its height and width if necessary. you can use your own tag. I have not checked carefully, so the application of this approach is your responsibility
UPD: working code in snippet. With the correct carriage behavior. The code that is responsible for the carriage is not mine, I took it from the Internet
Upvotes: 0
Reputation: 1141
You can solve this by using useRef and useLayoutEffect built-in hooks of react. This approach updates the height of the textarea before any rendering in the browser and therefor avoids any "visual update"/flickering/jumping of the textarea.
import React from "react";
const MIN_TEXTAREA_HEIGHT = 32;
export default function App() {
const textareaRef = React.useRef(null);
const [value, setValue] = React.useState("");
const onChange = (event) => setValue(event.target.value);
React.useLayoutEffect(() => {
// Reset height - important to shrink on delete
textareaRef.current.style.height = "inherit";
// Set height
textareaRef.current.style.height = `${Math.max(
textareaRef.current.scrollHeight,
MIN_TEXTAREA_HEIGHT
)}px`;
}, [value]);
return (
<textarea
onChange={onChange}
ref={textareaRef}
style={{
minHeight: MIN_TEXTAREA_HEIGHT,
resize: "none"
}}
value={value}
/>
);
}
https://codesandbox.io/s/react-textarea-auto-height-s96b2
Upvotes: 14
Reputation: 2885
Here's a simple solution that doesn't involve refs. The textarea
is dynamically adusted using some CSS and the rows
attribute. I used this myself, recently (example: https://codesandbox.io/embed/q8174ky809).
In your component, grab the textarea
, calculate the current number of rows, and add 1:
const textArea = document.querySelector('textarea')
const textRowCount = textArea ? textArea.value.split("\n").length : 0
const rows = textRowCount + 1
return (
<div>
<textarea
rows={rows}
placeholder="Enter text here."
onKeyPress={/* do something that results in rendering */}
... />
</div>
)
And in your CSS:
textarea {
min-height: 26vh; // adjust this as you see fit
height: unset; // so the height of the textarea isn't overruled by something else
}
Upvotes: 7
Reputation: 158
You can check the repo. Or you can add the package to your project.
https://github.com/andreypopp/react-textarea-autosize
Also if you really willing to learn how the logic working exactly;
https://github.com/andreypopp/react-textarea-autosize/blob/master/src/calculateNodeHeight.js
There is a source code with all calculations together.
Upvotes: 0