React TS - Max character limit for textarea - javascript

So I have 2 issues. I've created a textarea component that will also keep track of how many characters the user has typed into the textarea.
Issue 1:
I'm trying to see if the current input length the user has typed is more than or equal to(>=) my maxLength prop if (value.length >= maxLength), but I get this error:
var maxLength: number | undefined
Object is possibly 'undefined'.ts(2532)
Could somebody explain why I'm getting this?
Issue 2:
Currently when you type a single character into the textarea, the character limit will remain at 0, and will only start to increment after the second character has been typed. Also if you type some text in the textatea and then delete it, it says the character limit is 1/X when it should be 0/X. How can I fix this?
Code:
import * as React from "react";
import "./styles.css";
interface ITextarea {
maxLength?: number;
}
const Textarea: React.FC<ITextarea> = ({ maxLength }) => {
const [value, setValue] = React.useState("");
const [charLimit, setCharLimit] = React.useState(`0 / ${maxLength}`);
const handleCharLimit = () => {
if (value.length >= maxLength) {
setCharLimit("You have reached the maximum number of characters!");
} else {
setCharLimit(`${value.length} / ${maxLength}`);
}
};
return (
<>
<textarea
value={value}
onChange={event => {
setValue(event.target.value);
handleCharLimit();
}}
maxLength={maxLength}
/>
{maxLength && <span>{charLimit}</span>}
</>
);
};
export default function App() {
return (
<div className="App">
<Textarea maxLength={50} />
</div>
);
}
Here's a CodeSandBox, forks are appretiated :)

Object is possibly 'undefined'.ts(2532)
ITextarea defines maxLength as an optional parameter. If you don't pass a value for it, its value will be undefined.
This happens because React state updates aren't immediate, they happen on the next render pass. You're calling handleCharLimit, but when you invoke it, value hasn't actually been updated yet, even though it's called after setValue. You should be using useEffect or useMemo to update your charLimit in response to a change in value.
See an updated version here: https://codesandbox.io/s/hungry-fermi-bsmny

Related

Caret Jumping to End When Rejecting Input

I have an input for a value. I want to validate the new value entered by the user and prevent the value from appearing in the input if it's invalid. For instance, the value should be numeric, and hence can only contain numbers, dots, and commas.
I have a validation function which returns true/false depending on whether a value meets the criteria (works as intended):
function validateInput(input) {
let allow = true
const splitValuesByDecimal = input.split(',')
if(allow) allow = /^[\d|,|.]*?$/.test(input) // allow only digits, dots, and commas
if(allow) allow = splitValuesByDecimal.length <= 2 // allow only 1 decimal separator (comma)
if(allow && splitValuesByDecimal.length > 1) allow = splitValuesByDecimal.at(-1).length <= 2 // allow only maximum of 2 decimal characters
return allow
}
In my input field, I call a custom handleChange function which ensures that the new input is valid before storing it in the state variable.
Rendering input
<input value={inputVal} onChange={handleChange} />
Handling input change
function handleChange(e) {
const value = e.target.value
if(validateInput(value)) {
setInputVal(value)
}
}
If an invalid input is inserted and validation fails, the value in my input field does not change (as intended), but the caret jumps to the end of the input field (not desirable).
Attempted Solution
I have tried to fix the caret jumping behaviour by adopting this answer where I created a cursor state variable which keeps track of the intended caret position:
function MyComponent() {
const [inputVal, setInputVal] = useState('')
const [cursor, setCursor] = useState(null)
const inputRef = useRef()
useEffect(() => {
if (inputRef.current) inputRef.current.setSelectionRange(cursor, cursor);
}, [inputRef, cursor, inputVal])
function validateInput(input) {
...
}
function handleChange(e) {
const value = e.target.value
if(validateInput(value)) {
setCursor(e.target.selectionStart)
setInputVal(value)
}
else {
setCursor(e.target.selectionStart - 1)
}
}
return (
<input
ref={inputRef}
value={inputVal}
onChange={handleChange} />
)
}
With this solution, the caret persists at the correct position the first time an invalid value is entered, but as I keep on subsequently pressing the same key inserting invalid value over and over again, after the first time, the caret moves back to the end of the input. This is because the useEffect is not called the second and subsequent times.
How can I make this work so that caret always stays at the same position regardless of how many times the user attempts to insert an invalid value?
Update :-
Sorry my bad, I haven't looked at the question completely. However what I was saying is also correct but not in current case. Please find the updated code below. Below code should also be more performant as it does not involve re-renders on every change
import React, { useState, useRef } from 'react';
export function App(props) {
const inputRef = useRef()
const oldVal = useRef(null)
function validateInput(input) {
let allow = true
const splitValuesByDecimal = input.split(',')
if(allow) allow = /^[\d|,|.]*?$/.test(input) // allow only digits, dots, and commas
if(allow) allow = splitValuesByDecimal.length <= 2 // allow only 1 decimal separator (comma)
if(allow && splitValuesByDecimal.length > 1) allow = splitValuesByDecimal.at(-1).length <= 2 // allow only maximum of 2 decimal characters
console.log(allow)
return allow
}
function handleChange(e) {
const valueNew = e.target.value
const pos = e.target.selectionStart - 1
if(validateInput(valueNew)) {
oldVal.current = valueNew
} else {
inputRef.current.value = oldVal.current
inputRef.current.setSelectionRange(pos, pos)
}
}
return (
<div key='mydiv' className='App'>
<input key='MyFixedKeyValue' ref={inputRef} onChange={handleChange} />
</div>
);
}
I also changed the same on playcode code link
React re-renders the whole component on setstate call. In your case values are being kept as it is a controlled input. But behind the scene react is basically creating a new input field every time you are entering a value. To force react to use the same field pass key prop with constant value.
<input
key='MyFixedKeyValue'
ref={inputRef}
value={inputVal}
onChange={handleChange} />

React input field value issue [duplicate]

This question already has answers here:
Why is setState in reactjs Async instead of Sync?
(8 answers)
The useState set method is not reflecting a change immediately
(15 answers)
Closed 9 months ago.
const [inputValue,setInputValue] = useState("")
handleChange = (e)=>{
setInputValue(e.target.value)
console.log(e.target.value,inputValue)
}
this is a simple input tag onChange function but the thing here inside the handleChange function when am logging the values e.target.value is what am typing on the field and inputValue which is the state am setting is actually empty so if i type let's say 'd' in the input field the value of the state is actualy empty and the next time i type something now it has the value 'd' which i have typed on previously...pls help me solve this
Yes. State change is async operation. console.log immediately executed. That mean console.log print the inputValue before the state update. That why if doing second its showing previous value.
Better you can detect the state change using useEffect
useEffect(()=>{
console.log(inputValue)
},[inputValue])
"the thing is that i want to do a search bar and because of this when i type let's say "a" or something search doesn't work because the state value is empty that time."
Setting state is an async process. When you try to log the state immediately after setting it all you're doing is logging the existing state before the component has re-rendered.
#prasanth is correct with their answer that adding a useEffect to watch for changes in the input state is the only way to log that new state.
If you're trying to write a search bar here's a quick contrived solution that filters out animal names from an array.
const { useState } = React;
function Example({ data }) {
// Initialise the input state
const [ input, setInput ] = useState('');
// When the input value changes
// update the state
function handleChange(e) {
setInput(e.target.value);
}
// `filter` the data using the input state,
// and then `map` over that array to return
// an array of JSX
function getFiltered(input) {
return data
.filter(el => el.toLowerCase().includes(input))
.map(el => <p>{el}</p>);
}
return (
<div>
<input
type="text"
placeholder="Find an animal"
onChange={handleChange}
value={input}
/>
<div>{getFiltered(input)}</div>
</div>
);
}
const data=["Alpine Goat","Beaver","Carolina Parakeet","Dragonfish","Eurasian Wolf","Frilled Lizard","Gila Monster","Honduran White Bat","Immortal Jellyfish","Japanese Macaque","Kooikerhondje","Leopard Gecko","Megalodon","Nova Scotia Duck Tolling Retriever","Orb Weaver","Pink Fairy Armadillo","Rhombic Egg-Eater Snake","Satanic leaf-tailed gecko","Tibetan Fox","Umbrellabird","Vervet Monkey","Whoodle","Xoloitzcuintli","Yeti Crab","Zonkey"];
ReactDOM.render(
<Example data={data} />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Undefined array.lenght when passed as an argument to a function in reactjs

In the below code function GetRandomInt takes an input max from function App. The supposed output console.log should just print the argument value. But it prints out Undefined in the console.
const GetRandomInt = ({max}) => {
console.log(max)
return Math.floor(Math.random() * (max))
}
const Button = (props) => {
return (
<button onClick={props.handleClick}>
{props.text}
</button>
)
}
const Display = (props) => {
return (
<div>
{props.text}
</div>
)
}
const App = () => {
const anecdotes = [
'If it hurts, do it more often',
'Adding manpower to a late software project makes it later!',
'The first 90 percent of the code accounts for the first 10 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.',
'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.',
'Premature optimization is the root of all evil.',
'Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.',
'Programming without an extremely heavy use of console.log is same as if a doctor would refuse to use x-rays or blood tests when diagnosing patients'
]
const [selected, setSelected] = useState(0)
const len = anecdotes.length
console.log(len)
const handleClick = () => {
console.log(Math.floor(Math.random() * (anecdotes.length)))
const index = GetRandomInt(len) // Math.floor(Math.random() * (anecdotes.length))
while (index === selected) {
index = GetRandomInt(len) // Math.floor(Math.random() * (anecdotes.length))
}
setSelected(index)
}
return (
<div>
<h1>Anecdote of the day</h1>
<Display text={anecdotes[selected]} />
<Button handleClick={handleClick} text={'Next anecdote'} />
{/* {anecdotes[selected]} */}
</div>
)
}
The above code prints three values to the console
assigned variable that contains array length
Direct usage of array length to create a random integer
Passed value to function to achieve the same in step 2 using function
Can someone tell me why this is happening? Am I passing the value incorrectly either datatype mismatch or something? I am a newbie in reactjs and learning through a course and this is one of the exercises where I am stuck.
The flaw is in your GetRandomInt function. It currently expects an object with the max property, and you are passing a number to it.
Rewrite it as
const GetRandomInt = (max) => {
console.log(max)
return Math.floor(Math.random() * (max))
}
What the curly braces around the parameter name essentially do is telling the function "Take the property max of the first argument you are given", while in your case you want the argument itself, and not its max property.
You can read more about object destructuring here

How do I properly handle 'NaN' value in input type='number' in React?

Just creating a simple currency converter (React + Typescript).
Everything works good. But I'm bit misunderstanding 1 thing: how to clear input (where you enter amount of money) dynamically properly?
Code:
const App = () => {
...
const [amount, setAmount] = useState(1)
const [amountInFromCurrency, setAmountInFromCurrency] = useState(true)
let fromAmount, toAmount
if (amountInFromCurrency) {
fromAmount = amount
toAmount = parseFloat((amount * exchangeRate).toFixed(2))
} else {
toAmount = amount
fromAmount = parseFloat((amount / exchangeRate).toFixed(2))
}
return (
<Card
amount={fromAmount}
/>
<Card
amount={toAmount}
/>
)
}
const Card = ({ amount }) => {
return (
<input type="number" value={amount || 0} onChange={changeInput}/>
)
}
When I start clean the input to the end it becomes 0 in both inputs as expected. But when I start entering the numbers in the input after cleaning it (when it shows 0) they start appearing after 0 but not instead. Example: shows "0", I start entering the numbers, shows "0123" instead "123". The 0 disapears only if I highlight all the numbers with cursor and enter another number or it disappears if I push inner/outer-spin-buttons in input.
As you can see my approach here for input value is value={amount || 0}. If I put only value={amount} the mistake that 'You have NaN in value' appears when I clean input to the end. I don't need that mistake.
What I need: cleaning inputs fully without mistake or if I show 0 after cleaning I need to disapper it correctly when I start entering other numbers like "123" but not "0123"
UPDATE:
Code for input change:
const handleFromAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setAmount(parseInt(e.target.value))
setAmountInFromCurrency(true)
}
const handleToAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setAmount(parseInt(e.target.value))
setAmountInFromCurrency(false)
}

How can I create a numeric field where the focus out adds 3 decimal digits?

I have a text field:
<Grid item xs={12} sm={6}>
<BzTextField
fullWidth
label="dec"
value={company.dex}
/>
</Grid>
what I am trying to do is to create a new component so I can create a new input type number which must add 3 decimal digits (ex. 3.3 becomes 3.300). I am having problem with how to implement something like this. Should I use Hooks or is there an easier way.
also the validation should be onChange and not on keyUp.
You can do it by creating a controlled input and using input's onBlur feature, which will execute onBlur function when you are done editing the input. This function will change add 3 decimal digits to the number.
function App() {
const [input, setInput] = React.useState("")
const onBlur = () => {
setInput(parseFloat(input).toFixed(3))
}
return (
<div >
<body>
<input
type="number"
value={input}
onChange={e=>setInput(e.target.value)}
onBlur={onBlur}
/>
</body>
</div>
);
}
Try This:
const x = 3.3;
const y = x.toFixed(3);
console.log(y);
// result 3.300
Explanation:
here toFixed(n) method used to extend value.
Where n is the number you want to extend value of number.
Click here for more info
Here's one way you could do it using material. I made the assumption that you don't want the text formatted until focus leaves the control. The onChange event in material fires on every key press so this makes it difficult to format using that event alone.
import React, { useState } from 'react';
import { TextField } from '#material-ui/core';
const BzTextField = ({ defaultValue = '' }) => {
const [value, setValue] = useState(defaultValue);
const handleBlur = (e) => {
let parsedValue = parseFloat(e.target.value);
if (parsedValue) {
setValue(parsedValue.toFixed(3));
}
}
return (
<TextField
type="number"
fullWidth
label="dec"
value={value}
onBlur={handleBlur}/>
);
};
export default BzTextField;

Categories

Resources