Split Lines function bug - ReactJS - javascript

I'm having the most bizarre problem on my project : I use a custom function to split the lines in spans while the components are mounted, here is the said function
function splitLines(container, opentag, closingtag, className) {
let spans = container.children,
top = 0,
tmp = "";
container.innerHTML = container.textContent.replace(
/\S+/g,
`<span class='${className} inlineBlock'>$&</span>`
);
for (let i = 0; i < spans.length; i++) {
let rect = spans[i].getBoundingClientRect().y;
if (top < rect) {
tmp += closingtag + opentag;
top = rect;
}
tmp += spans[i].outerHTML + " ";
}
container.innerHTML = tmp + closingtag;
}
export default splitLines
it's not totally working , indeed, the lines are not correctly splitted :
but when I change the page and then come back, the problem is solved (but if I refresh, we're back to square one)
Am I not triggering the function as soon as the component is mounted ? Is the function flawed ?
If you have any idea how I could resolve my issue please share them :)
useEffect(() => {
gsap.registerPlugin(ScrollTrigger);
splitLines(
presentationTextRef.current,
"<span class= 'hidden inlineBlock'>",
"</span>",
"presWords"
);
const presTL = gsap.timeline({
scrollTrigger: {
trigger: "#Presentation",
start: "top center",
end: "bottom center",
id: "Presentation",
toggleActions: "play reverse restart reverse",
},
});
const presWords = gsap.utils.toArray("#Presentation .presWords");
presTL.from(presWords, {
yPercent: 200,
stagger: 0.05,
});
return () => {
presTL.kill();
};
}, []);

I found a solution that is more fitted I think to the reactjs way of things (meaning : that doesn't manipulate the DOM directly).
Here it is
import React, { useEffect, useRef } from "react";
// gsap
import gsap, { Power3 } from "gsap";
export default function AnimatedWords(props) {
const { text, delay } = props;
const words = text.split(" ");
const wordsRef = useRef([]);
useEffect(() => {
gsap.to(wordsRef.current, {
y: 0,
stagger: 0.05,
delay: delay ? delay : 0,
ease: Power3.easeOut,
});
});
return (
<span>
{words.map((word, idx) => (
<span style={{ display: "inline-block", overflow: "hidden" }}>
<span
key={idx}
ref={(el) => (wordsRef.current[idx] = el)}
className="animatedLetter"
style={{
display: "inline-block",
transform: "translate(0, 200px)",
}}
>
{/* '&nbsp' is used to create spaces between the words, it produces a non-breaking space*/}
{word}
</span>
</span>
))}
</span>
);
}
What do you think ? use refs in a map function is always such a hassle, I wonder if I should have used createRef ?
Thanks anyway !

Related

React-spring not animating as expected

So I'm using react-visibility-sensor with react-spring to animate text sliding char by char from any side I want.
In my home page the animation is running smoothly, I use it twice one from the right side and another from the top side.
When I switch routes and go to another page the animation does not work.
I have my code divided in an "Title" component "Char" component and a custom hook "useAnimatedText".
Title component:
import React from "react";
import VisibilitySensor from "react-visibility-sensor";
import useAnimatedText from "../../hooks/useAnimatedText";
import Char from './Char'
const Title = ({title, side}) => {
// HERE I CALL A CUSTOM HOOK THAT WILL DIFINE IF THE ELEMENT IS VISIBLE OR NOT
// AND HANDLE THE ANIMATION WHEN NECESSARY
const [isVisible, onChange, objArray] = useAnimatedText(title)
let elements = objArray.map((item, i) => {
return(
<Char
key={i}
isVisible={isVisible}
item={item}
delay={400 + (i * 40)}
side={side}
/>
)
})
console.log(isVisible)
return(
<VisibilitySensor onChange={onChange} >
<span className="title-box">
<h1 className="my-heading divided-heading">
{elements}
</h1>
<hr className="title-ruller"></hr>
</span>
</VisibilitySensor>
)
}
export default Title
Char component:
import { useSpring, animated } from "react-spring"
const Char = (props) => {
const { isVisible, item, delay, isBouncy, side} = props
const [ref, addBounce] = useBounce()
let springConfig = {}
if (side === 'right') {
springConfig = {
to: {
opacity: isVisible ? 1 : 0,
translateX : isVisible ? '0px' : '1000px'
},
config: { mass:2, tension: 200, friction: 30},
delay: delay
}
}
else if (side === 'top') {
springConfig = {
to: {
opacity: isVisible ? 1 : 0,
translateY: isVisible ? '0px' : '-500px'
},
config:{ mass:2, tension: 250, friction: 35},
delay: delay
}
}
const spring = useSpring({...springConfig})
return(
<animated.span
style={ spring }
className={isVisible ? 'is-visible' : 'is-not-visible'}
>
{item.char === ' ' ? <span> </span> : item.char}
</animated.span>
)
}
export default Char
This is the custom Hook:
import { useState } from "react";
import { stringToArray } from '../helpers'
// HOOK THAT HANDLES THE TEXT ANIMATION BY SETTING A STATE OF VISIBILITY
function useAnimatedText(string) {
const [isVisible, setVisibility] = useState(false);
const onChange = visiblity => {
visiblity && setVisibility(visiblity);
};
let objArray = stringToArray(string)
return [isVisible, onChange, objArray]
}
export default useAnimatedText
I did a console.log(isVisible) and the value was true but it was rendering in the page the spring values as if it was false(not visible).
I really canĀ“t understand where I'm going wrong here, the only problem I have is when I'm not at my main route, could it be because of react-router-dom?
If someone has any clue, let me know.

React Native: Fade through a series of images using Animated & opacity

I'm currently trying to fade through a series of images. Basically, I always want to display one image at a time, then animate its opacity from 1 to 0 and that of the next image in the series from 0 to 1, and so on. Basically something like this, which I've already implemented for the web in ReactJS and CSS animations:
However, I seem to keep getting stuck on using React Native's Animated library and refs. I've tried storing the opacity of all the images in an array which itself is contained in an useRef hook. Then, using Animated, I'm trying to perform two parallel animations which change the opacity of the current image index and that of the next index. This is what I've come up with:
export default function StartImageSwitcher() {
const images = Object.values(Images).map((img) => img.imageNoShadow);
const [currentImage, setCurrentImage] = useState(0);
const opacity = useRef<Animated.Value[]>([
new Animated.Value(1),
...Array(images.length - 1).fill(new Animated.Value(0)),
]).current;
useEffect(() => {
let nextImage = currentImage + 1;
if (nextImage >= images.length) nextImage = 0;
Animated.parallel([
Animated.timing(
opacity[currentImage],
{
toValue: 0,
duration: 2000,
useNativeDriver: true,
},
),
Animated.timing(
opacity[nextImage],
{
toValue: 1,
duration: 2000,
useNativeDriver: true,
},
),
]).start(() => {
setCurrentImage(nextImage);
});
}, [currentImage]);
images.map((image, index) => console.log(index, opacity[index]));
return (
<View style={styles.imageWrapper}>
{
images.map((image, index) => (
<Animated.Image style={{ ...styles.image, opacity: opacity[index] }} source={image} key={index} />
))
}
</View>
);
}
However, this doesn't seem to work at all. When mounted, it only shows the first image, then fades that one out and all the other images in and gets stuck there:
Anyone got an idea where I messed up? I feel like I'm not using the useRef() hook in combination with the Animated library like I'm supposed to.
Your solution is pretty clever, and it generally looks like it should work to me. Performance might take a hit though as the number of images increases. I thought of an alternate method: partition the images array and then use setInterval to alternate between two Animated.Images, which get their source from each array.
// from https://stackoverflow.com/questions/41932345/get-current-value-of-animated-value-react-native
const getAnimatedValue = (value: Animated.Value) => Number.parseInt(JSON.stringify(value));
export default function StartImageSwitcher() {
// partition images into two arrays
const images1 = [];
const images2 = [];
Object.values(Images).forEach((img, i) => {
(i % 1 === 0 ? images1 : images2).push(img.imageNoShadow)
});
// use refs for the indexes so values don't become stale in the setInterval closure
const images1Index = useRef(0);
const images2Index = useRef(0);
const image1Opacity = useRef(new Animated.Value(1)).current;
const image2Opacity = useRef(new Animated.Value(0)).current;
useEffect(() => {
const swapImageInterval = setInterval(() => {
const newImage1Opacity = getAnimatedValue(image1Opacity) === 1 ? 0 : 1;
const newImage2Opacity = getAnimatedValue(image2Opacity) === 1 ? 0 : 1;
Animated.parallel([
Animated.timing(image1Opacity, {
toValue: newImage1Opacity,
duration: 2000,
useNativeDriver: true,
}),
Animated.timing(image2Opacity, {
toValue: newImage2Opacity,
duration: 2000,
useNativeDriver: true,
}),
]).start(() => {
if (newImage1Opacity === 1) {
// image 2 is now faded out, so we can swap out its source
const nextIndex = images2Index.current === images2.length - 1 ? 0 : images2Index.current + 1;
images2Index.current = nextIndex;
} else {
// image 1 is faded out, so we can swap out its source
const nextIndex = images1Index.current === images1.length - 1 ? 0 : images1Index.current + 1;
images1Index.current = nextIndex;
}
})
}, 5000)
return () => clearInterval(swapImageInterval);
}, [images1Index, images2Index]);
return (
<View style={styles.imageWrapper}>
<Animated.Image style={{ ...styles.image, opacity: image1Opacity }} source={images1[images1Index.current]} />
<Animated.Image style={{ ...styles.image, opacity: image2Opacity }} source={images2[images2Index.current]} />
</View>
);
}

React Carousel rotate on delay issue when setting state in parent component causing rerender?

Hopefully, the title isn't too ambiguous, but I am struggling how to summarise my issue in just a few words.
What I am attempting to achieve is the one image with appear for 1.5 seconds, the opacity of image will go from 0.3 to 1 and a string of text "Completed" appears below the image, and will then rotate and move to the next image where it will have the same outcome, constantly looping through each image.
The issue is, it only seems to rotate once. It will update the styles and add the text below the image but fail to rotate.
I have a 3d carousel component that rotates the items within an array logos when a function is called rotate().
const [rotateDeg, setRotateDeg] = useState(0);
const [currentIndex, setCurrentIndex] = useState(0);
const rotate = () => {
const maxIndex = logos.length - 1;
const incrementIndex = currentIndex + 1;
const newIndex = incrementIndex > maxIndex ? 0 : incrementIndex;
setCurrentIndex(newIndex);
setRotateDeg(rotateDeg - 360 / logos.length);
};
return (
<Container>
<Carousel ref={carousel} logosLength={logos.length} rotateDeg={rotateDeg}>
{logos.map((item, index) => {
const { key } = item;
return (
<Item
key={key}
index={index}
logosLength={logos.length}
currentIndex={currentIndex}
>
<LoadingSpinLogo
item={item}
delay={1500 * (index + 1)}
rotate={() => rotate()}
key={key}
isCurrent={currentIndex === index}
/>
</Item>
);
})}
</Carousel>
</Container>
);
The component LoadingSpinLogo is where I seem to be having an issue.
I have tried to pass different dependencies into the useEffect callback but it seems to cause weird issues with the delay.
const LoadingSpinLogo = ({ item, rotate, delay, isCurrent }) => {
const [completed, setCompleted] = useState(false);
const [divStyle, setDivStyle] = useState({ opacity: 0.3 });
const props = useSpring(divStyle);
const customStyles = {
height: "78px",
width: "115px",
backgroundRepeat: "no-repeat",
backgroundPosition: "center center",
display: "block",
margin: "auto"
};
const updateCompleted = () => {
setCompleted(true);
setDivStyle({ opacity: 1, from: { opacity: 0.3 } });
rotate();
};
useEffect(() => {
const timeoutID = setTimeout(function() {
updateCompleted();
}, delay);
return () => {
// Clean up the subscription
window.clearInterval(timeoutID);
};
}, []);
return (
<LoadingSpinContainer>
<animated.div style={props}>
<ImageServer png={item.url} customStyles={customStyles} />
</animated.div>
{completed ? "Completed" : null}
</LoadingSpinContainer>
);
};
Here is a CodeSanbox of my components.
Any idea where I am going wrong here?
Any help would be greatly appreciated.
Removing the [] in your useEffect function seems to works fine for me
useEffect(() => {
const timeoutID = setTimeout(function() {
updateCompleted();
}, delay);
return () => {
// Clean up the subscription
window.clearInterval(timeoutID);
};
}, );

In Codepen, how do you import a React component from one pen into another?

I have two pens, and I'm trying to use a React component I defined in one pen inside another, but I'm not clear on how Codepen actually handles React imports between pens. I went to the destination pen and added the source pen's address to the Javascript references, but I don't know how to proceed from there. I can get this to work in a local node project using traditional export, but the Codepen element is giving me trouble. Here's the code:
SOURCE (https://codepen.io/ejpg/pen/LmOVoR):
export default class Wheel extends React.Component // Export default causes error
{
constructor(props){
super(props);
this.state = {
spin : false,
value: 0
};
this.spin = this.spin.bind(this);
}
spin(e){
var val = this.state.value + 720 + (Math.floor(Math.random() * 24) * 15);
console.log((this.state.value % 360) / 15);
e.target.style.webkitTransform = 'rotate(' + -val + 'deg)';
e.target.style.webkitTransition = '-webkit-transform 4s ease-out';
this.setState({value: val});
}
render(){
const wheelVals = [800, "BANKRUPT", 200, 300, 350, 250, 400, 300, 200, 250, 500, 350, 250,
"BANKRUPT", 200, 300, 400, 250, 600, "LOSE A TURN", 200, 300, 250, 200];
return (<div><img width="400" height="400" src="https://orig00.deviantart.net/0a38/f/2010/242/f/6/singapore_wheel_of_fortune_by_wheelgenius-d2xmb9v.jpg" onClick={(e) => this.spin(e)}/><br/><br/>{wheelVals[(this.state.value % 360) / 15]}
</div>);
}
}
DESTINATION (https://codepen.io/ejpg/pen/bMgWpN):
let { Grid, Row, Col, ButtonToolbar, Button } = ReactBootstrap;
// How do I import the class?
class CustomButton extends React.Component {
onHandleClick = () => {
this.props.onClick();
};
render(){
return <Button bsStyle={this.props.bsStyle} onClick={this.onHandleClick}><strong>{this.props.text}</strong></Button>;
}
}
class Letter extends React.Component {
onHandleClick = () => {
this.props.onClick(this.props.letter);
};
render () {
const style = { border: '1px solid black',
display: 'inline-block',
fontSize: '3.5vw',
width: '4vw',
height: '10vh',
textAlign: 'center',
whiteSpace: 'no-wrap',
overflow: 'hidden'};
if (this.props.letter === ' ') style.border = '';
return (
<div
style={style}
key={this.props.key}
onClick={this.onHandleClick} // Have to pass onClick to div
>
{this.props.letter}
</div>
);
}
}
class MyComponent extends React.Component {
constructor(props) {
super(props);
var blanks = '';
for (var i = 0; i < this.props.answer.length; ++i)
{
this.props.answer[i] === ' ' ?
blanks += ' ': blanks += '-';
}
this.state = {
phrase: blanks,
alpha: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
bonus: false,
revealed: false
};
this.callLetter = this.callLetter.bind(this);
this.bonusRound = this.bonusRound.bind(this);
this.complete = this.complete.bind(this);
}
replaceAt(str, index, replacement) {
return str.substr(0, index) + replacement + str.substr(index + replacement.length);
}
complete(){
if (this.state.revealed === false)
{
this.setState({phrase: this.props.answer, revealed: true});
}
}
checkForLetter(letter, phr)
{
this.setState((prevState, props) => {
var prephrase = prevState.phrase;
var index = phr.indexOf(letter);
while( index !== -1)
{
prephrase = this.replaceAt(prephrase, index, letter);
index = phr.indexOf(letter, index + 1);
}
return ({phrase: prephrase});
});
}
callLetter(letter) {
this.setState((prevState, props) => {
var alphaclone = prevState.alpha;
var letterindex = alphaclone.indexOf(letter);
alphaclone = alphaclone.slice(0, letterindex) + alphaclone.slice(letterindex + 1);
return ({alpha: alphaclone});
});
this.checkForLetter(letter, this.props.answer);
}
bonusRound(){
if (this.state.bonus === false)
{
this.callLetter('R');
this.callLetter('S');
this.callLetter('T');
this.callLetter('L');
this.callLetter('N');
this.callLetter('E');
this.setState({bonus: true});
}
}
render() {
return (
<Grid>
<Row className="show-grid" >
{
this.state.phrase.split(' ').map((item, j) =>
(
<div style = {{display:'inline-block'}}>
<Letter letter = {' '}/>
{item.split('').map((item, i) =>
(
<Letter letter= {item}/>
)) }
</div>
))
}
</Row>
<Row className="show-grid" style={{margin: '3vh'}}>
{
this.state.alpha.split('').map((item, i) =>
(
<Letter letter={item} key={i} onClick={this.callLetter}/>
))
}
</Row>
<Row className="show-grid" style={{margin: '3vh'}}>
<ButtonToolbar>
<CustomButton bsStyle = {"primary"} text= {"BONUS ROUND"} onClick = {this.bonusRound}/>
<CustomButton bsStyle = {"danger"} text= {"REVEAL ALL"} onClick = {this.complete}/>
</ButtonToolbar>
</Row>
</Grid>
);
}
}
ReactDOM.render(
<MyComponent answer='A VERY VERY EXCESSIVELY LONG TEST'/>,
document.getElementsByClassName('container-fluid')[0]
);
Any help is greatly appreciated.
EDIT: I can't believe I actually have to explicitly state that I don't want to copy and paste it.
For that you have to make your pen containing the component as a module. You can do this by going to Settings > Javascript and checking the Add type="module" checkbox.
Then you can import the component in another pen using the URL of your pen:
import MyComponent from 'https://codepen.io/user/pen/xyz.js';
The entire doc regarding this may be found here: https://blog.codepen.io/2017/12/26/adding-typemodule-scripts-pens/.
Hope this helps :)

javascript/react dynamic height textarea (stop at a max)

What I'm trying to achieve is a textarea that starts out as a single line but will grow up to 4 lines and at that point start to scroll if the user continues to type. I have a partial solution kinda working, it grows and then stops when it hits the max, but if you delete text it doesn't shrink like I want it to.
This is what I have so far.
export class foo extends React.Component {
constructor(props) {
super(props);
this.state = {
textareaHeight: 38
};
}
handleKeyUp(evt) {
// Max: 75px Min: 38px
let newHeight = Math.max(Math.min(evt.target.scrollHeight + 2, 75), 38);
if (newHeight !== this.state.textareaHeight) {
this.setState({
textareaHeight: newHeight
});
}
}
render() {
let textareaStyle = { height: this.state.textareaHeight };
return (
<div>
<textarea onKeyUp={this.handleKeyUp.bind(this)} style={textareaStyle}/>
</div>
);
}
}
Obviously the problem is scrollHeight doesn't shrink back down when height is set to something larger. Any suggestion for how I might be able to fix this so it will also shrink back down if text is deleted?
ANOTHER SIMPLE APPROACH (without an additional package)
export class foo extends React.Component {
handleKeyDown(e) {
e.target.style.height = 'inherit';
e.target.style.height = `${e.target.scrollHeight}px`;
// In case you have a limitation
// e.target.style.height = `${Math.min(e.target.scrollHeight, limit)}px`;
}
render() {
return <textarea onKeyDown={this.handleKeyDown} />;
}
}
The problem when you delete the text and textarea doesn't shrink back is because you forget to set this line
e.target.style.height = 'inherit';
Consider using onKeyDown because it works for all keys while others may not (w3schools)
In case you have padding or border of top or bottom. (reference)
handleKeyDown(e) {
// Reset field height
e.target.style.height = 'inherit';
// Get the computed styles for the element
const computed = window.getComputedStyle(e.target);
// Calculate the height
const height = parseInt(computed.getPropertyValue('border-top-width'), 10)
+ parseInt(computed.getPropertyValue('padding-top'), 10)
+ e.target.scrollHeight
+ parseInt(computed.getPropertyValue('padding-bottom'), 10)
+ parseInt(computed.getPropertyValue('border-bottom-width'), 10);
e.target.style.height = `${height}px`;
}
I hope this may help.
you can use autosize for that
LIVE DEMO
import React, { Component } from 'react';
import autosize from 'autosize';
class App extends Component {
componentDidMount(){
this.textarea.focus();
autosize(this.textarea);
}
render(){
const style = {
maxHeight:'75px',
minHeight:'38px',
resize:'none',
padding:'9px',
boxSizing:'border-box',
fontSize:'15px'};
return (
<div>Textarea autosize <br/><br/>
<textarea
style={style}
ref={c=>this.textarea=c}
placeholder="type some text"
rows={1} defaultValue=""/>
</div>
);
}
}
or if you prefer react modules https://github.com/andreypopp/react-textarea-autosize
Just use useEffect hook which will pick up the height during the renderer:
import React, { useEffect, useRef, useState} from "react";
const defaultStyle = {
display: "block",
overflow: "hidden",
resize: "none",
width: "100%",
backgroundColor: "mediumSpringGreen"
};
const AutoHeightTextarea = ({ style = defaultStyle, ...etc }) => {
const textareaRef = useRef(null);
const [currentValue, setCurrentValue ] = useState("");// you can manage data with it
useEffect(() => {
textareaRef.current.style.height = "0px";
const scrollHeight = textareaRef.current.scrollHeight;
textareaRef.current.style.height = scrollHeight + "px";
}, [currentValue]);
return (
<textarea
ref={textareaRef}
style={style}
{...etc}
value={currentValue}
onChange={e=>{
setCurrentValue(e.target.value);
//to do something with value, maybe callback?
}}
/>
);
};
export default AutoHeightTextarea;
Really simple if you use hooks "useRef()".
css:
.text-area {
resize: none;
overflow: hidden;
min-height: 30px;
}
react componet:
export default () => {
const textRef = useRef<any>();
const onChangeHandler = function(e: SyntheticEvent) {
const target = e.target as HTMLTextAreaElement;
textRef.current.style.height = "30px";
textRef.current.style.height = `${target.scrollHeight}px`;
};
return (
<div>
<textarea
ref={textRef}
onChange={onChangeHandler}
className="text-area"
/>
</div>
);
};
you can even do it with react refs. as setting ref to element
<textarea ref={this.textAreaRef}></textarea> // after react 16.3
<textarea ref={textAreaRef=>this.textAreaRef = textAreaRef}></textarea> // before react 16.3
and update the height on componentDidMount or componentDidUpdate as your need. with,
if (this.textAreaRef) this.textAreaRef.style.height = this.textAreaRef.scrollHeight + "px";
actually you can get out of this with useState and useEffect
function CustomTextarea({minRows}) {
const [rows, setRows] = React.useState(minRows);
const [value, setValue] = React.useState("");
React.useEffect(() => {
const rowlen = value.split("\n");
if (rowlen.length > minRows) {
setRows(rowlen.length);
}
}, [value]);
return (
<textarea rows={rows} onChange={(text) => setValue(text.target.value)} />
);
}
Uses
<CustomTextarea minRows={10} />
I like using this.yourRef.current.offsetHeight. Since this is a textarea, it wont respond to height:min-content like a <div style={{height:"min-content"}}>{this.state.message}</div> would. Therefore I don't use
uponResize = () => {
clearTimeout(this.timeout);
this.timeout = setTimeout(
this.getHeightOfText.current &&
this.setState({
heightOfText: this.getHeightOfText.current.offsetHeight
}),
20
);
};
componentDidMount = () => {
window.addEventListener('resize', this.uponResize, /*true*/)
}
componentWillUnmount = () => {
window.removeEventListener('resize', this.uponResize)
}
but instead use
componentDidUpdate = () => {
if(this.state.lastMessage!==this.state.message){
this.setState({
lastMessage:this.state.message,
height:this.yourRef.current.offsetHeight
})
}
}
on a hidden div
<div
ref={this.yourRef}
style={{
height:this.state.height,
width:"100%",
opacity:0,
zIndex:-1,
whiteSpace: "pre-line"
})
>
{this.state.message}
</div>
Using hooks + typescript :
import { useEffect, useRef } from 'react';
import type { DetailedHTMLProps, TextareaHTMLAttributes } from 'react';
// inspired from : https://stackoverflow.com/a/5346855/14223224
export const AutogrowTextarea = (props: DetailedHTMLProps<TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>) => {
const ref = useRef<HTMLTextAreaElement>(null);
let topPadding = 0;
let bottomPadding = 0;
const resize = () => {
ref.current.style.height = 'auto';
ref.current.style.height = ref.current.scrollHeight - topPadding - bottomPadding + 'px';
};
const delayedResize = () => {
window.setTimeout(resize, 0);
};
const getPropertyValue = (it: string) => {
return Number.parseFloat(window.getComputedStyle(ref.current).getPropertyValue(it));
};
useEffect(() => {
[topPadding, bottomPadding] = ['padding-top', 'padding-bottom'].map(getPropertyValue);
ref.current.focus();
ref.current.select();
resize();
}, []);
return <textarea ref={ref} onChange={resize} onCut={delayedResize} onPaste={delayedResize} onDrop={delayedResize} onKeyDown={delayedResize} rows={1} {...props} />;
};
import { useRef, useState } from "react"
const TextAreaComponent = () => {
const [inputVal, setInputVal] =useState("")
const inputRef = useRef(null)
const handleInputHeight = () => {
const scrollHeight = inputRef.current.scrollHeight;
inputRef.current.style.height = scrollHeight + "px";
};
const handleInputChange = () => {
setInputVal(inputRef.current.value)
handleInputHeight()
}
return (
<textarea
ref={inputRef}
value={inputVal}
onChange={handleInputChange}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleSubmit(e);
inputRef.current.style.height = "40px";
}
}}
/>
)}
Extremely simple solution:
function allowTextareasToDynamicallyResize() {
let textareas = document.getElementsByTagName('textarea');
for (let i = 0; i < textareas.length; i++) {
textareas[i].style.height = textareas[i].scrollHeight + 'px';
textareas[i].addEventListener('input', (e) => {
e.target.style.height = textareas[i].scrollHeight + 'px';
});
}
}
// Call this function in the componentDidMount() method of App,
// or whatever class you want that contains all the textareas
// you want to dynamically resize.
This works by setting an event listener to all textareas. For any given textarea, new input will trigger a function that resizes it. This function looks at any scrollHeight, i.e. the height that is overflowing out of your existing container. It then increments the textarea height by that exact height. Simple!
As mentioned in the comment, you have to call this function in some method, but the important part is that you call it AFTER everything is mounted in React / populated in JS. So the componentDidMount() in App is a good place for this.

Categories

Resources