I'm using this example to set the height of the textarea.
but the size of the textarea is not set automatically if the textarea has a value.
https://codesandbox.io/s/autosize-textarea-forked-wdowvr?file=/src/App.tsx
import { useRef, useState } from "react";
import "./styles.css";
export default function App() {
const [value, setValue] = useState("");
const textAreaRef = useRef<HTMLTextAreaElement>(null);
const handleChange = (evt) => {
const val = evt.target?.value;
setValue(val);
};
return (
<div className="App">
<label htmlFor="review-text">Review:</label>
<textarea
id="review-text"
onChange={handleChange}
onInput={(e: React.FormEvent<HTMLTextAreaElement>) => {
e.currentTarget.style.height = e.currentTarget.scrollHeight + "px";
}}
ref={textAreaRef}
rows={1}
value={value}
/>
</div>
);
}
for example
how can i fix this issue?
You can use the following snippets to adjust the height on loading the component.
Just for demo purposes:
const [value, setValue] = useState(
"already has a value with\nmore text\nand even more"
);
const textAreaRef = useRef<HTMLTextAreaElement>(null);
You can access the ref once it is set. useEffect will do the trick here.
useEffect(() => {
if (textAreaRef && textAreaRef.current) {
textAreaRef.current.style.height = textAreaRef.current.scrollHeight + 'px';
}
}, []);
https://codesandbox.io/s/autosize-textarea-forked-symr7p?file=/src/App.tsx
You also need to set the height once the DOM is mounted. This way it can respond to whatever value is in there before any user input.
I did this with the useEffect hook. (Don't forget to import it from react).
I also tweaked the method of changing the height a bit, because it wasn't working properly.
import { useRef, useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [value, setValue] = useState("");
const textAreaRef = useRef<HTMLTextAreaElement>(null);
const handleChange = (evt) => {
const val = evt.target?.value;
setValue(val);
};
const resizeTextarea = (t: HTMLTextAreaElement) => {
t.style.height = "auto";
const p = parseFloat(window.getComputedStyle(t, null).getPropertyValue("padding-top"))
+ parseFloat(window.getComputedStyle(t, null).getPropertyValue("padding-bottom"));
t.style.height = t.scrollHeight - p + "px";
}
const handleInput = (e: React.FormEvent<HTMLTextAreaElement>) => {
resizeTextarea(e.currentTarget);
}
useEffect(() => {
const t = textAreaRef.current;
if (t != null) {
t.style.overflowY = 'hidden';
resizeTextarea(t);
}
},[]);
return (
<div className="App">
<label htmlFor="review-text">Review:</label>
<textarea
id="review-text"
onChange={handleChange}
onInput={handleInput}
ref={textAreaRef}
rows={1}
value={value}
/>
</div>
);
}
https://codesandbox.io/s/autosize-textarea-forked-s39ur2?file=/src/App.tsx:0-1172
Related
I'm trying to make app that will take the value of input element and use this value in inc/dec component. I have 2 components and I use props.
Component have input and a set button.
Component have inc/dec buttons and reset button
I'm enter number to first component set it with button. Then I want to inc/dec it in second component and I have NaN error. When I reset it the element take the value of first component and after that inc/dec works well.
first component:
import React, { useState } from "react";
import Sample from "../Sample/Sample";
const SetSample = () => {
const [value, setValue] = useState();
const formChange = (e) => {
e.preventDefault();
}
const handlerChange = () => {
const foo = document.querySelector("input").value * 1;
setValue(foo);
}
return (
<div>
<form onSubmit={formChange}>
<input type="text" />
<button type="submit" onClick={handlerChange}>Set start value</button>
</form>
<p>Start value is: {value || 'Please enter a number'}</p>
<hr />
<Sample start={value} />
</div>
);
}
export default SetSample;
second component:
import React, { useState } from "react";
const Sample = (props) => {
const point = props.start;
const [counter = 0, setCounter] = useState(point);
const decrement = () => {
setCounter(counter - 1);
}
const increment = () => {
setCounter(counter + 1);
}
const reset = () => {
setCounter(point);
}
return (
<div>
<button onClick={decrement}>-</button>
<div>{counter}</div>
<button onClick={increment}>+</button>
<div><button onClick={reset}>reset</button></div>
</div>
);
}
export default Sample;
It seems to me that the problem is in counter and point values.
You first have to import useEffect hook from react, then in the dependency array, you have to enter your point variable.
Nothing interacts with your Sample.jsx after you click Set Start Value.
And when you click the reset button there is some interaction.
Due to that and you are able to see the number only after clicking reset.
import React, { useState , useEffect } from "react";
const Sample = (props) => {
const point = props.start;
const [counter = 0, setCounter] = useState(point);
// edited here
useEffect(()=>{
setCounter(props.start);
},[point]);
const decrement = () => {
setCounter(counter - 1);
}
const increment = () => {
setCounter(counter + 1);
}
const reset = () => {
setCounter(point);
}
return (
<div>
<button onClick={decrement}>-</button>
<div>{counter}</div>
<button onClick={increment}>+</button>
<div><button onClick={reset}>reset</button></div>
</div>
);
}
export default Sample;
In the first render you need to set the value of the input to be 0 just not to run into NaN error in SetSample component:
const [value, setValue] = useState(0);
and in Sample component you need to set the counter if it changes using useEffect and don't set 0 for it because it already has 0 from props.start:
const point = props.start;
const [counter, setCounter] = useState(point);
useEffect(() => {
setCounter(point);
}, [point]);
currently I am making a navbar that only shows when you scroll up, to prevent useEffect to run everytime when the visible state get changed, I had to use both a ref and a state that is synced together to do comparison in the useEffect, using ref and a state to keep track of a same value seems extremely fishy, is there another way of doing this? one that does not involve triggering useEffect from creating the event handlers everytime the state changes?
import React, { useState, useRef, useEffect } from 'react';
import Link from 'next/link';
const NavbarLink = ({ name, href }: { name: string, href: string }) => {
return (
<Link href={href}>
<a>{ name }</a>
</Link>
);
}
const Navbar = () => {
const scrollYRef = useRef(0);
const visibleRef = useRef(true);
const [visible, setVisible] = useState(true);
useEffect(() => {
const onScroll = (event: Event) => {
event.preventDefault();
if ((window.scrollY < scrollYRef.current) != visibleRef.current) {
visibleRef.current = !visibleRef.current;
setVisible(x => !x);
}
scrollYRef.current = window.scrollY;
}
window.addEventListener('scroll', onScroll);
return () => {
window.removeEventListener('scroll', onScroll);
}
}, []);
return (
<div className={`${!visible && '-translate-y-full'} fixed flex w-full h-32 font-bold text-white transition-all`}>
<NavbarLink name="home" href='/'/>
</div>
);
}
You could use the "latest ref" pattern (here via react-use's useLatest hook), which boxes the latest value of a state atom at each component update time, so you don't need to manage it manually, and you don't need it as a dependency for the hook. (I'd also grab useEvent from react-use if you end up using it.)
import React, { useState, useRef, useEffect } from "react";
import Link from "next/link";
// via https://github.com/streamich/react-use/blob/master/src/useLatest.ts
const useLatest = (value) => {
const ref = useRef(value);
ref.current = value;
return ref;
};
const NavbarLink = ({ name, href }: { name: string; href: string }) => {
return (
<Link href={href}>
<a>{name}</a>
</Link>
);
};
function useVisibleOnScrollUp(initial = true) {
const scrollYRef = useRef(window.scrollY);
const [visible, setVisible] = useState(initial);
const latestVisibleRef = useLatest(visible);
useEffect(() => {
const onScroll = (event: Event) => {
event.preventDefault();
const currentVisible = latestVisibleRef.current;
if (window.scrollY < scrollYRef.current != currentVisible) {
setVisible((x) => !x);
}
scrollYRef.current = window.scrollY;
};
window.addEventListener("scroll", onScroll);
return () => {
window.removeEventListener("scroll", onScroll);
};
}, []);
return visible;
}
const Navbar = () => {
const visible = useVisibleOnScrollUp(true);
return (
<div
className={`${
!visible && "-translate-y-full"
} fixed flex w-full h-32 font-bold text-white transition-all`}
>
<NavbarLink name="home" href="/" />
</div>
);
};
There's a solution here... Several, actually - but none of them work for React 17.0.2. They all result in
Error: Rendered more hooks than during the previous render.
Even with fixes listed in the comments (Using useref() instead of useState, for instance).
So my question is - how can I have long click/press/tap in React 17.0.2 and newer?
My attempt at fixing it:
//https://stackoverflow.com/questions/48048957/react-long-press-event
import {useCallback, useRef, useState} from "react";
const useLongPress = (
onLongPress,
onClick,
{shouldPreventDefault = true, delay = 300} = {}
) => {
//const [longPressTriggered, setLongPressTriggered] = useState(false);
const longPressTriggered = useRef(false);
const timeout = useRef();
const target = useRef();
const start = useCallback(
event => {
if (shouldPreventDefault && event.target) {
event.target.addEventListener("touchend", preventDefault, {
passive: false
});
target.current = event.target;
}
timeout.current = setTimeout(() => {
onLongPress(event);
//setLongPressTriggered(true);
longPressTriggered.current = true;
}, delay);
},
[onLongPress, delay, shouldPreventDefault]
);
const clear = useCallback(
(event, shouldTriggerClick = true) => {
timeout.current && clearTimeout(timeout.current);
shouldTriggerClick && !longPressTriggered && onClick(event);
//setLongPressTriggered(false);
longPressTriggered.current = false;
if (shouldPreventDefault && target.current) {
target.current.removeEventListener("touchend", preventDefault);
}
},
[shouldPreventDefault, onClick, longPressTriggered]
);
return {
onMouseDown: e => start(e),
onTouchStart: e => start(e),
onMouseUp: e => clear(e),
onMouseLeave: e => clear(e, false),
onTouchEnd: e => clear(e)
};
};
const isTouchEvent = event => {
return "touches" in event;
};
const preventDefault = event => {
if (!isTouchEvent(event)) return;
if (event.touches.length < 2 && event.preventDefault) {
event.preventDefault();
}
};
export default useLongPress;
RandomItem.js:
import React, {useEffect, useState} from 'react';
import Item from "../components/Item";
import Loader from "../../shared/components/UI/Loader";
import {useAxiosGet} from "../../shared/hooks/HttpRequest";
import useLongPress from '../../shared/hooks/useLongPress';
function RandomItem() {
let content = null;
let item = useAxiosGet('collection');
if (item.error === true) {
content = <p>There was an error retrieving a random item.</p>
}
if (item.loading === true) {
content = <Loader/>
}
if (item.data) {
const onLongPress = useLongPress();
return (
content =
<div>
<h1 className="text-6xl font-normal leading-normal mt-0 mb-2">{item.data.name}</h1>
<Item name={item.data.name} image={item.data.filename} description={item.data.description}/>
</div>
)
}
return (
<div>
{content}
</div>
);
}
export default RandomItem;
The (unedited) useLongPress function should be used similar to the following example:
import React, { useState } from "react";
import "./styles.css";
import useLongPress from "./useLongPress";
export default function App() {
const [longPressCount, setlongPressCount] = useState(0)
const [clickCount, setClickCount] = useState(0)
const onLongPress = () => {
console.log('longpress is triggered');
setlongPressCount(longPressCount + 1)
};
const onClick = () => {
console.log('click is triggered')
setClickCount(clickCount + 1)
}
const defaultOptions = {
shouldPreventDefault: true,
delay: 500,
};
const longPressEvent = useLongPress(onLongPress, onClick, defaultOptions);
return (
<div className="App">
<button {...longPressEvent}>use Loooong Press</button>
<span>Long press count: {longPressCount}</span>
<span>Click count: {clickCount}</span>
</div>
);
}
Be sure to pass in the onLongPress function, onClick function, and the options object.
Here is a codesandbox with React 17.0.2 with a working example of useLongPress: https://codesandbox.io/s/uselongpress-forked-zmtem?file=/src/App.js
I am trying to get the textarea to be focused on so the user doesn't have to click start then click on the textarea, but keep getting an error when i click start, using codesandbox IDE.
Bit stuck now so not sure what i can do to fix it, the code runs fine when inputRef.current.focus and inputRef.current.disabled aren't included in the startGame() function.
import React, { useState, useEffect, useRef } from "react";
import "./styles.css";
export default function App() {
const STARTING_TIME = 5;
const [text, updateText] = useState("");
const [time, setTime] = useState(STARTING_TIME);
const [isTimeRunning, setIsTimeRunning] = useState(false);
const [wordCount, setWordCount] = useState(0);
const inputRef = useRef(null)
function handleChange(event) {
const { value } = event.target;
updateText(value);
}
function startGame() {
setIsTimeRunning(true);
setTime(STARTING_TIME);
updateText("");
setWordCount(0);
inputRef.current.disabled = false;
inputRef.current.focus();
}
function endGame() {
setIsTimeRunning(false);
const numWords = countWords(text);
setWordCount(numWords);
}
function countWords(text) {
const wordsArr = text.trim().split(" ");
const filteredWords = wordsArr.filter(word => word !== "");
return filteredWords.length;
}
useEffect(() => {
if (isTimeRunning && time > 0) {
setTimeout(() => {
setTime(time => time - 1);
}, 1000);
} else if (time === 0) {
endGame();
}
}, [time, isTimeRunning]);
return (
<div>
<h1>Speed Typing Game</h1>
<textarea
onChange={handleChange}
value={text}
disabled={!isTimeRunning}
/>
<h4>Time remaning: {time} </h4>
<button type="button" onClick={startGame} disabled={isTimeRunning}>
Start Game
</button>
<h1>Word Count: {wordCount} </h1>
</div>
);
}
Errors shown in the IDE console log below:
You have to attach the inputRef to your textArea.
<textarea
onChange={handleChange}
value={text}
disabled={!isTimeRunning}
ref={inputRef}
/>
Find out more about React Refs here
As shown below, this TextInput component does a simple job: when input's value is empty, hides the title because placeholder shows the same words.
But the code doesn't work as expected. InputEvent does run, but reassign activeStyle has no effect.
import React, {useState} from 'react';
import './TextInput.css';
import * as CSS from 'csstype';
type TextInputProps = {
title: string
}
const TextInput: React.FC<TextInputProps> = ({title, children}) => {
const hiddenStyle: CSS.Properties = {
opacity: 0
};
const visibleStyle: CSS.Properties = {
opacity: 1
};
let activeStyle = hiddenStyle
const [rawTextInput, setRawTextInput] = useState("")
const InputEvent = (e: React.FormEvent<HTMLInputElement>) => {
const inputValue = e.currentTarget.value;
setRawTextInput(inputValue)
if(inputValue == ""){
activeStyle = hiddenStyle
} else {
activeStyle = visibleStyle
}
}
return (
<div className="TextInput">
<p
className="TextInputTitle"
style={activeStyle}
>
{title}
</p>
<input
className="TextInputField"
type="text"
placeholder={title}
value={rawTextInput}
onChange={InputEvent}
/>
{/*<p className="TextInputHint"></p>*/}
</div>
);
}
export default TextInput
import React, {useState} from 'react';
import './TextInput.css';
import * as CSS from 'csstype';
type TextInputProps = {
title: string
}
const TextInput: React.FC<TextInputProps> = ({title, children}) => {
const hiddenStyle: CSS.Properties = {
opacity: 0
};
const visibleStyle: CSS.Properties = {
opacity: 1
};
let activeStyle = hiddenStyle
const [rawTextInput, setRawTextInput] = useState("")
const InputEvent = (e: React.FormEvent<HTMLInputElement>) => {
const inputValue = e.currentTarget.value;
setRawTextInput(inputValue)
if(inputValue == ""){
activeStyle = hiddenStyle
} else {
activeStyle = visibleStyle
}
}
return (
<div className="TextInput">
<p
className="TextInputTitle"
style={activeStyle}
>
{title}
</p>
<input
className="TextInputField"
type="text"
placeholder={title}
value={rawTextInput}
onChange={InputEvent}
/>
{/*<p className="TextInputHint"></p>*/}
</div>
);
}
export default TextInput
Local variable doesn't affect re-render.
let activeStyle = hiddenStyle //local variable
You need to keep this in state and change using setter method.
const [activeStyle, setActiveStyle] = useState(hiddenStyle)
const InputEvent = (e: React.FormEvent<HTMLInputElement>) => {
const inputValue = e.currentTarget.value;
setRawTextInput(inputValue)
if(inputValue == ""){
setActiveStyle(hiddenStyle)
} else {
setActiveStyle(visibleStyle)
}
}
I've tried a simpler approach
import React, { useState } from "react";
type TextInputProps = {
title: string;
};
const TextInput: React.FC<TextInputProps> = ({ title, children }) => {
const [rawTextInput, setRawTextInput] = useState("");
const InputEvent = (e: React.FormEvent<HTMLInputElement>) => {
setRawTextInput(e.target.value)
};
return (
<div className="TextInput">
<p className="TextInputTitle" style={{opacity : rawTextInput == "" ? 0 : 1}}>
{title}
</p>
<input
className="TextInputField"
type="text"
placeholder={title}
value={rawTextInput}
onChange={InputEvent}
/>
{/*<p className="TextInputHint"></p>*/}
</div>
);
};
export default TextInput;
I hope it'll be useful
Changing the value of a local variable does not cause a re-render. And even if something else causes a render, that new render won't have access to local variables from the previous render. You need to use useState for the active style, not a local variable.
just use useState for activeStyle instead of declaring it on the scope of the component,this could cause unsync effect which could be a problem and indeterministic
import React, {useState} from 'react';
import './TextInput.css';
import * as CSS from 'csstype';
type TextInputProps = {
title: string
}
const TextInput: React.FC<TextInputProps> = ({title, children}) => {
const hiddenStyle: CSS.Properties = {
opacity: 0
};
const visibleStyle: CSS.Properties = {
opacity: 1
};
const [rawTextInput, setRawTextInput] = useState("")
//------Put something you want to change dynamically inside useState()
const [titleStyle, setTitleStyle] = useState(hiddenStyle)
//------
const InputEvent = (e: React.FormEvent<HTMLInputElement>) => {
const inputValue = e.currentTarget.value;
setRawTextInput(inputValue)
if(inputValue == ""){
setTitleStyle(hiddenStyle)
} else {
setTitleStyle(visibleStyle)
}
}
return (
<div className="TextInput">
<p
className="TextInputTitle"
style={titleStyle}
>
{title}
</p>
<input
className="TextInputField"
type="text"
placeholder={title}
value={rawTextInput}
onChange={InputEvent}
/>
{/*<p className="TextInputHint"></p>*/}
</div>
);
}
export default TextInput