Click files to view code →
Create a text input component that counts and displays the number of characters and words as the user types. This is commonly used in forms with length limits, social media posts, SMS messages, and blog platforms.
[!TIP] Goal
Use React's
useState[!IMPORTANT] Requirements
No external dependencies needed.
Key Topics:
Plan of Action:
CharacterCounterimport React, { useState } from 'react';
import './CharacterCounter.css';
function CharacterCounter({
maxLength = 200,
showWordCount = true,
placeholder = "Start typing...",
label = "Enter text"
}) {
const [text, setText] = useState('');
// Calculate character count
const charCount = text.length;
// Calculate word count
const wordCount = text.trim().length === 0
? 0
: text.trim().split(/\s+/).length;
// Calculate remaining characters
const remainingChars = maxLength - charCount;
// Determine warning level
const getWarningLevel = () => {
const percentage = (charCount / maxLength) * 100;
if (percentage >= 100) return 'exceeded';
if (percentage >= 90) return 'danger';
if (percentage >= 75) return 'warning';
return 'normal';
};
const warningLevel = getWarningLevel();
const handleChange = (e) => {
const newText = e.target.value;
// Only update if under max length or deleting
if (newText.length <= maxLength) {
setText(newText);
}
};
return (
<div className="character-counter-container">
<label htmlFor="text-input" className="counter-label">
{label}
</label>
<textarea
id="text-input"
className={`text-input ${warningLevel}`}
// ...CSS Styling (CharacterCounter.css):
.character-counter-container {
width: 100%;
max-width: 600px;
margin: 1rem auto;
font-family: Arial, sans-serif;
}
.counter-label {
display: block;
margin-bottom: 0.5rem;
font-size: 1rem;
font-weight: 600;
color: #333;
}
.text-input {
width: 100%;
padding: 1rem;
font-size: 1rem;
font-family: inherit;
border: 2px solid #e0e0e0;
border-radius: 8px;
resize: vertical;
transition: border-color 0.3s ease;
outline: none;
}
.text-input:focus {
border-color: #2196f3;
box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);
}
.text-input.warning {
border-color: #ff9800;
}
.text-input.danger {
border-color: #f44336;
}
.text-input.exceeded {
border-color: #d32f2f;
background-color: #ffebee;
}
.counter-info {
margin-top: 0.75rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
// ...Usage Example:
import React from 'react';
import CharacterCounter from './CharacterCounter';
function App() {
return (
<div className="app">
<h1>Tweet Composer</h1>
<CharacterCounter
maxLength={280}
showWordCount={true}
placeholder="What's happening?"
label="Your tweet"
/>
<h2>SMS Message</h2>
<CharacterCounter
maxLength={160}
showWordCount={false}
placeholder="Enter your message"
label="Text message"
/>
</div>
);
}Best Practices:
Common Pitfalls:
Enhancements:
Advanced Version with More Features:
import React, { useState, useMemo } from 'react';
function AdvancedCharacterCounter({ maxLength = 200 }) {
const [text, setText] = useState('');
// Memoize expensive calculations
const stats = useMemo(() => {
const trimmedText = text.trim();
return {
characters: text.length,
charactersNoSpaces: text.replace(/\s/g, '').length,
words: trimmedText.length === 0 ? 0 : trimmedText.split(/\s+/).length,
sentences: trimmedText.length === 0 ? 0 : trimmedText.split(/[.!?]+/).filter(s => s.trim().length > 0).length,
paragraphs: trimmedText.length === 0 ? 0 : trimmedText.split(/\n\n+/).filter(p => p.trim().length > 0).length,
readingTime: Math.ceil(trimmedText.split(/\s+/).length / 200) // 200 words per minute
};
}, [text]);
const handleChange = (e) => {
const newText = e.target.value;
if (newText.length <= maxLength) {
setText(newText);
}
};
const handleClear = () => {
setText('');
};
return (
<div className="advanced-counter">
<div className="input-section">
<textarea
value={text}
onChange={handleChange}
placeholder="Start typing..."
rows={8}
/>
<button onClick={handleClear} disabled={!text}>
Clear
</button>
</div>
<div className="stats-grid">
<div className="stat-card">
<div className="stat-value">{stats.characters}</div>
<div className="stat-label">Characters</div>
</div>
<div className="stat-card">
// ...Word Count with Better Accuracy:
// More accurate word counting function
function countWords(text) {
if (!text || text.trim().length === 0) return 0;
// Remove multiple spaces and trim
const cleaned = text.trim().replace(/\s+/g, ' ');
// Split by spaces and filter out empty strings
const words = cleaned.split(' ').filter(word => word.length > 0);
return words.length;
}
// Alternative using regex for complex cases
function countWordsRegex(text) {
if (!text || text.trim().length === 0) return 0;
// Match word boundaries, including hyphenated words and contractions
const matches = text.match(/\b[\w'-]+\b/g);
return matches ? matches.length : 0;
}Testing Considerations:
Real-World Use Cases:
This character counter component is essential for any application with text input constraints and provides immediate feedback to improve user experience.
No files open
Click a file in the explorer to view