Document file-local sub-component pattern as preferred over render*() helpers - #46213
Document file-local sub-component pattern as preferred over render*() helpers#46213nulmete wants to merge 3 commits into
Conversation
… helpers Adds a patterns.md section explaining when to prefer a file-local sub-component (declared below the main component) over a renderSomething() closure helper, and the why behind the preference. Relaxes @typescript-eslint/no-use-before-define so the main-on-top / sub-components-below shape doesn't trip the rule. Demonstrates the pattern by refactoring renderTargetsCount in SelectTargets into a <TargetsCount /> sub-component with an explicit props interface.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #46213 +/- ##
==========================================
+ Coverage 66.83% 66.99% +0.15%
==========================================
Files 2755 3254 +499
Lines 220204 223041 +2837
Branches 10916 11895 +979
==========================================
+ Hits 147176 149423 +2247
- Misses 59735 60319 +584
- Partials 13293 13299 +6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| } | ||
|
|
||
| const TargetsCount = ({ | ||
| baseClass, |
There was a problem hiding this comment.
perhaps we can avoid passing baseClass and just have this component have its own top/base class
There was a problem hiding this comment.
It looks like it's passed in to the main component, so I think we have to have it here if we want to stick with the pattern of namespacing classes by appending __my-class to the base.
There was a problem hiding this comment.
I don't think we need the explanation of why this pattern is preferred. At most I would add a sentence like "This improves readability and makes it easier to extract that component into a separate file if needed" to the first paragraph.
The note of
render*()helpers are still acceptable, especially in existing code — don't
refactor an entire page just to migrate. New code should prefer the
sub-component pattern.
would be more appropriate in a Claude rules file, but even then I would only add it if we find it doing unnecessary refactors.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughThis PR relaxes the TypeScript ESLint rule by adding an override for *.tsx and *.jsx that sets 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR documents a preferred frontend pattern (“file-local sub-components” under the main component) and updates an existing LiveQuery component to follow that pattern, alongside an ESLint configuration change intended to support it.
Changes:
- Add a new documentation section recommending file-local sub-components over
render*()helper closures. - Refactor
SelectTargetsto replace arenderTargetsCount()helper with a file-localTargetsCountsub-component. - Relax
@typescript-eslint/no-use-before-defineto permit referencing bottom-of-file components.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
frontend/docs/patterns.md |
Documents the preferred file-local sub-component pattern. |
frontend/components/LiveQuery/SelectTargets.tsx |
Refactors a JSX helper closure into a file-local sub-component. |
.eslintrc.js |
Adjusts linting rules to allow referencing declarations before they appear in the file. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Allow referencing functions and variables (e.g. React components defined | ||
| // at the bottom of the file) before they are declared. This supports the | ||
| // "main component on top, helper sub-components below" pattern documented | ||
| // in frontend/docs/patterns.md. | ||
| "@typescript-eslint/no-use-before-define": [ |
|
Thanks for getting this going! I put this in draft since I think we need to chat about it more as a team. I really think extracting TargetsCount does read better than the 60-line closure it replaced and a real solution about how we handle the long tail of render helpers. I ran a quick audit across frontend/ to ground the discussion, and I'd love a group discussion before we land the documented pattern. Audit of
Distribution: A few things I wanted to share: ~72% of our render* helpers are under 40 lines, which feels like the zone where they exist precisely because sub-component extraction would cost more than it saves — closing over baseClass, isFetchingCounts, counts, etc. is the whole appeal. I wonder if recommending the sub-component pattern as the default might push the boilerplate onto a lot of small helpers where the tradeoff isn't quite there. I don't think I'm quite on board with that. The PR's example (~62 lines) sits in the long tail rather than the typical case, so it's a great extraction candidate — but it might not be the strongest argument for "always prefer this." Re: placement - The rest of the codebase leans on declarations-before-use as the reading order, and putting sub-components below their usage means readers have to scroll past to see what it does which is a anti-pattern. Would it work to place the extracted sub-component above the main component instead? That would keep the helpers-above convention consistent and let us drop the ESLint override entirely, which I think is the part I'm most cautious about, it opts the whole JSX/TSX surface out of forward-reference protection, which feels like a wide change to enable one ordering choice. A possible middle-ground for our patterns:
Here's the top 10 candidates Claude found if we want a follow-up cleanup pass (these are the ones the data really argues for componentizing or extracting to their own file):
Those are all well past the "should be a real component (or its own file)" line, and they'd be a great proving ground for an updated convention we settle on here. Super appreciate you kicking this off and looking forward to another group chat on it! |
|
@RachelElysia thanks for your review! For context, these were my main arguments/thoughts when I put this up:
I don't know what specifically required
I agree 👍 , and I'm not saying that we should choose one or the other just because one method produces less/more LOC than the other. For example, as it was pointed out in your comment above, those top 10 candidates that Claude brought up might deserve a quick look to determine how readable they are for us, and maybe split some of them up.
I think either approach makes sense. I wouldn't mind having it at the top -- IMHO that's even more readable than a
Sounds good 👍
Agreed 👍
Yeah, this is the main concern for me. These TL;DR:
|
|
Closing this since we agreed on doing what I mentioned in the TL;DR |

Modified patterns.md to document that we'll prefer plain JSX components over
render*()closure functions for inner components.Testing
Summary by CodeRabbit
Chores
Refactor