-
Notifications
You must be signed in to change notification settings - Fork 96
fix: Address eslint react-hook violations in CharacterCount #3321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -65,7 +65,7 @@ export const CharacterCount = ({ | |
| getMessage = defaultMessage, | ||
| ...remainingProps | ||
| }: TextInputCharacterCountProps | TextareaCharacterCountProps): JSX.Element => { | ||
| const initialCount = getCharacterCount(value || defaultValue) | ||
| const [initialCount] = useState(getCharacterCount(value || defaultValue)) | ||
| const [length, setLength] = useState(initialCount) | ||
| const [message, setMessage] = useState(getMessage(initialCount, maxLength)) | ||
| const [isValid, setIsValid] = useState(initialCount < maxLength) | ||
|
|
@@ -76,17 +76,21 @@ export const CharacterCount = ({ | |
| 'usa-character-count__status--invalid': !isValid, | ||
| }) | ||
|
|
||
| useEffect(() => { | ||
| const message = getMessage(length, maxLength) | ||
| setMessage(message) | ||
| const [prevLength, setPrevLength] = useState(length) | ||
| if (length !== prevLength) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This matches the current implementation, only updating the message and validation status when the length changes. A more complete revision would respect an isolated change to the <CharacterCount
getMessage={new Date() >= modern_requirements_date ? modernMessageMaker : legacyMessageMaker}
...props
> |
||
| setPrevLength(length) | ||
| setMessage(getMessage(length, maxLength)) | ||
| setIsValid(length <= maxLength) | ||
| } | ||
|
|
||
| useEffect(() => { | ||
| // Updates the character count status for screen readers after a 1000ms delay | ||
| const timer = setTimeout(() => { | ||
| // Setting the text directly for VoiceOver compatibility. | ||
| if (srMessageRef.current) srMessageRef.current.textContent = message | ||
| }, 1000) | ||
| return () => clearTimeout(timer) | ||
| }, [length]) | ||
| }, [message]) | ||
|
|
||
| const handleBlur = ( | ||
| e: | ||
|
|
@@ -97,7 +101,7 @@ export const CharacterCount = ({ | |
| ): void => { | ||
| const validationMessage = !isValid ? 'The content is too long.' : '' | ||
| e.target.setCustomValidity(validationMessage) | ||
| if (callback) callback(e) | ||
| callback?.(e) | ||
| } | ||
|
|
||
| const handleChange = ( | ||
|
|
@@ -107,12 +111,10 @@ export const CharacterCount = ({ | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| callback?: (e: any) => void | ||
| ): void => { | ||
| const { | ||
| target: { value = '' }, | ||
| } = e | ||
| const { value } = e.target | ||
| setLength(getCharacterCount(value)) | ||
|
|
||
| if (callback) callback(e) | ||
| callback?.(e) | ||
| } | ||
|
|
||
| let InputComponent: JSX.Element | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is fine, so not blocking, but out of curiosity: Why not use a ref here?
Something like
Admittedly, it looks way grosser, but is supposedly more semantically correct. Found some relevant discussion/examples here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brandonlenz - Argh, neither of our approaches is correct here. I'm glad you left a comment.
My approach failed at the one improvement it was supposed to achieve: only evaluate
getCharacterCount(value || defaultValue)once for the purpose of settinginitialCountduring the initial render. Use that value for the next fewuseState()lines and then forget about it. Because I passedgetCharacterCount(value || defaultValue)as a value touseState()rather than through an initializer function, it still gets evaluated every render. My change only achieved adding tiny overhead to each render cycle.Your approach does better at only evaluating
getCharacterCount(value || defaultValue)once like intended (kudos for spotting that and not assigning the value as theuseRef()argument). Unfortunately, it violates another principle: ref values are not supposed to be accessed during renders. Enabling react-hooks recommended eslint ruleset notes this:In 9d33668, I switch to using initializer functions for this
setStateand also for the messagesetState.