Our objective is to create a comment field that has a character limit and the remaining character number is displayed below. That information is updated on any change on this comment field, like key press, paste with the mouse. We also limit the characters to never go below 0, I don’t see a point to display negative remaining characters. Also the user has less work because there is no need to remove extra characters, the code does it for them.

I will display here the CSS and react/typescript code and after that I will explain what I crafted there.
CSS code:
p {
padding-bottom: 5px;
}
section {
width: 50%;
}
.comments {
width: 100%;
height: 100px;
padding: 10px;
box-sizing: border-box;
border-width: 5px;
border-radius: 7px;
margin-bottom: 5px;
}
.remainingChars {
text-align: right;
}
React code:
import { useState } from 'react';
interface CommentProps {
label?: string;
limit?: number;
}
const Comments = ({label='Speak your truth', limit=50}:CommentProps) => {
const [content, setContent] = useState('');
const handleChange = e => {
setContent(e.currentTarget.value.substring(0, limit));
}
return (
<section>
<p>Comment</p>
<textarea
className="comments"
onChange={handleChange}
placeholder={label}
value={content}
/>
<p className="remainingChars">{limit - content.length} characters remaining</p>
</section>
)
}
ReactDOM.render(<Comments limit="250" />, document.getElementById('app'));
The codePen link is this: https://codepen.io/jozsefk/pen/VwBPrEa
First of all we create an interface to define the type of the props that this component gets. After this I apply the Interface on the destructured props directly:
const Comments = ({
label='Speak your truth',
limit=50
}:CommentProps) => {...});
I also take good care of the props to have their default values in case nothing is specified when we call the component. This is how I called it, without a label prop:
<Comments limit="250" />
In the JSX, the textarea got an onChange event, because we need to take care not only of the keyUp event but also when user is pasting some content. The value attribute has a state value and the state is actually updated on every onChange event. We can call this operation a two-way-binding simulation, because React doesn’t have two-way binding natively.
<textarea
className="comments"
onChange={handleChange}
placeholder={label}
value={content}
/>
Anything that we write in the textarea will be updated in the state called content with the setState() function and JSX will rerender the new value. The only reason for this is, that if JSX is rerendered at each keystroke or each copy/paste, also the remaining characters calculation is recalculated and rerendered.
<p className="remainingChars">{limit - content.length} characters remaining</p>
I substracted the textarea’s character number from the maximum allowed characters number to get the remaining allowed number of characters.
So, what’s the deal with the handleChange method?
const handleChange = e => {
setContent(e.currentTarget.value.substring(0, limit));
}
In this function we update the value of the content state and the new value will never be more characters then the maximum characters number that we set as limit.
I hope you enjoyed this article and of course please like, comment and subscribe.