Cursor rule
.cursor/rules/react-readability.mdcReact code readability pass — apply after generating any React component
Cursor rules
Quality
57/100
Scores the file, not the repository.Length
1,396 words
10 headings · 5 code blocksRepository
0
— · pushed 112 days agoLast changed
3 days ago
First indexed 3 days ago.123456# React readability pass78After generating or modifying any React component, perform a dedicated readability9pass before considering the task complete. Do not combine this with implementation.10The sole job of this pass is to make the code easy to read at a glance — not to11change behaviour, not to refactor logic, not to add features.1213---1415## Principles1617**Code is written for the next reader, not the machine.**18A component should read top to bottom like a short document. Someone who has never19seen this file should be able to understand what it does within thirty seconds.2021**Names express intent, not implementation.**22A name should tell you what a thing *means* in the context of this component, not23what it technically is. `likesCount` over `likes`. `previousCommentsState` over24`prev`. `isLiked` over `liked`. If you have to read the surrounding lines to25understand a name, the name is wrong.2627**Whitespace is punctuation.**28A blank line means "these two groups of things are unrelated." No blank line means29"these things belong together." Blank lines are not decorative — use them only where30the grouping boundary is real. Never use more than one blank line between groups.3132**Handlers and helpers are different things. Name and group them accordingly.**33A handler responds to a user gesture and owns the try/catch, the toast, the34side-effect. A helper mutates state on behalf of a handler or a child component —35it is an imperative command, not an event response. They serve different purposes36and belong in separate groups.3738**Abstraction is earned, not assumed.**39Do not extract a function because it is more than N lines. Extract it when the40extraction has a name that is more expressive than the code it replaces, and when41that name makes the call site read more clearly. Otherwise, leave it inline.4243---4445## Component template4647Use this ordering for every component. Do not reorder sections.48Separate each section with exactly one blank line.4950```tsx51// ─── 1. Props destructuring ───────────────────────────────────────────────────52// Destructure immediately. Never access props.x anywhere below this block.53// Name every destructured value for what it means in this component, not what54// the parent called it if those differ.5556const {57 thumbnailUrl,58 username,59 id,60} = props.post;6162// ─── 2. State ─────────────────────────────────────────────────────────────────63// All useState calls together. Name state variables for the thing they hold,64// not for their setter. Context that the component "lives on" goes here too —65// it is incoming data, same category as state.6667const [likesCount, setLikesCount] = useState(Number(likes));68const [postComments, setPostComments] = useState(comments);69const [currentUser] = useContext(CurrentUserContext);7071// ─── 3. Derived values ────────────────────────────────────────────────────────72// Values computed from state or props. useMemo if expensive, plain const if not.73// Never derive inside JSX — give the value a name here so the return stays clean.7475const isPostOwner = currentUser?.username === username;76const hasComments = postComments.length > 0;7778// ─── 4. Effects ───────────────────────────────────────────────────────────────79// useEffect calls. One blank line between each effect if there are multiple.80// If an effect needs a comment to explain why it exists, write the comment.8182useEffect(() => {83 // reason this effect exists, if non-obvious84}, [dependency]);8586// ─── 5. Handlers ──────────────────────────────────────────────────────────────87// Functions that respond directly to user gestures (clicks, submits, changes).88// Naming convention: onVerb + Noun + Action — e.g. onClickLikePost, onSubmitLoginForm.89// Handlers own the try/catch, the toast, and any async side-effect.90// One blank line between each handler.9192const onClickDeletePost = async () => {93 try {94 const response = await deletePost(id);95 if (response) {96 toast.success("Post deleted!");97 }98 } catch (error) {99 console.error(error);100 toast.error("Failed to delete post. Please try again.");101 }102};103104const onClickLikePost = async () => {105 const likePayload = {106 user_id: currentUser.userID,107 post_id: id,108 };109 try {110 const response = await likePost(likePayload);111 if (response) {112 const isLiked = response.liked;113 if (isLiked) {114 setLikesCount((previousLikesCount) => previousLikesCount + 1);115 toast.success("Post liked!");116 } else {117 setLikesCount((previousLikesCount) => previousLikesCount - 1);118 toast.success("Post like removed :(");119 }120 }121 } catch (error) {122 console.error(error);123 toast.error("Failed to like or remove like from post. Please try again.");124 }125};126127// ─── 6. Helpers ───────────────────────────────────────────────────────────────128// Imperative state-mutation functions. Not event responses — these are commands129// called by handlers above or passed down to child components as props.130// Naming convention: verb + Noun — e.g. addCommentToPost, removeCommentFromPost.131// No try/catch here. No toasts. No side-effects. Pure state transitions only.132133const addCommentToPost = (comment) => {134 setPostComments((previousCommentsState) => [...previousCommentsState, comment]);135};136137const removeCommentFromPost = (commentId) => {138 setPostComments((previousCommentsState) =>139 previousCommentsState.filter((comment) => comment.id !== commentId)140 );141};142143const addShowMoreCommentsToPost = (newComments) => {144 setPostComments(newComments);145};146147// ─── 7. Return ────────────────────────────────────────────────────────────────148// One blank line before the return. JSX should be as flat as the design allows.149// No inline logic that needs a comment to understand — derive it in section 3.150// No anonymous functions in JSX — all handlers and helpers are named above.151152return (153 <div className="post">154 ...155 </div>156);157```158159---160161## Naming reference162163| Thing | Convention | Example |164|---|---|---|165| Event handler | `onVerb + Noun + Context` | `onClickLikePost`, `onSubmitLoginForm`, `onChangeSearchInput` |166| State helper / child prop | `verb + Noun` | `addCommentToPost`, `removeCommentFromPost`, `resetFormFields` |167| State variable | the thing it holds | `likesCount`, `postComments`, `isMenuOpen` |168| setState callback arg | `previous + StateName` | `previousLikesCount`, `previousCommentsState` |169| Boolean | reads as a question | `isLiked`, `hasComments`, `isPostOwner`, `canDelete` |170| Async response | `response` (always) | `const response = await likePost(payload)` |171| Extracted value from response | name what it means | `const isLiked = response.liked` |172| Payload / request body | `nounPayload` | `likePayload`, `commentPayload` |173174---175176## Before / after examples177178### Variable names179180```tsx181// Bad182const res = await fetch(url);183const data = await res.json();184const filtered = data.filter(x => x.active);185const [val, setVal] = useState(0);186187// Good188const response = await fetch(userProfileUrl);189const userProfiles = await response.json();190const activeProfiles = userProfiles.filter((profile) => profile.isActive);191const [likesCount, setLikesCount] = useState(0);192```193194### setState callback argument195196```tsx197// Bad — "prev" tells you nothing about what the state is198setLikesCount((prev) => prev + 1);199setPostComments((prev) => [...prev, comment]);200201// Good — the argument name matches the state variable name202setLikesCount((previousLikesCount) => previousLikesCount + 1);203setPostComments((previousCommentsState) => [...previousCommentsState, comment]);204```205206### Handler vs helper distinction207208```tsx209// Bad — a handler that does no error handling, and a "helper" that fires a toast210const likePost = async () => {211 await likePostApi(id); // no try/catch, no feedback212};213214const updateComments = (comment) => {215 setPostComments([...postComments, comment]);216 toast.success("Comment added!"); // toast does not belong in a helper217};218219// Good — handler owns the side-effect and feedback, helper is a pure state transition220const onClickLikePost = async () => {221 try {222 const response = await likePost(id);223 toast.success("Post liked!");224 } catch (error) {225 toast.error("Failed to like post. Please try again.");226 }227};228229const addCommentToPost = (comment) => {230 setPostComments((previousCommentsState) => [...previousCommentsState, comment]);231};232```233234### Whitespace grouping235236```tsx237// Bad — no visual separation, everything runs together238const [isOpen, setIsOpen] = useState(false);239const title = props.title;240useEffect(() => { document.title = title; }, [title]);241const onClickOpen = () => setIsOpen(true);242const filteredItems = items.filter(i => i.active);243244// Good — each group is visually distinct, reading order matches mental model245const { title, items } = props;246247const [isOpen, setIsOpen] = useState(false);248249const filteredItems = items.filter((item) => item.isActive);250251useEffect(() => {252 document.title = title;253}, [title]);254255const onClickOpen = () => setIsOpen(true);256```257258---259260## Readability pass checklist261262Run through this after every component generation before marking the task done:263264- [ ] Props are destructured at the top in a single block265- [ ] All state and context declarations are grouped together266- [ ] Derived values are named and computed before the return — none inside JSX267- [ ] Effects follow derived values268- [ ] Handlers are named `onVerb + Noun` and own their own try/catch and toasts269- [ ] Helpers are named `verb + Noun`, contain no toasts, no try/catch, no side-effects270- [ ] setState callbacks name the previous state argument (`previousXxx`)271- [ ] Booleans read as questions (`isX`, `hasX`, `canX`)272- [ ] One blank line between sections, no double blank lines273- [ ] No anonymous functions passed directly in JSX — all are named above274- [ ] No name requires reading surrounding code to understand275
Also in VictorGoic0/Math-Tutoring-App
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| VictorGoic0/Math-Tutoring-App.cursor/rules/api-patterns.mdc · 0 | Cursor rules | setupstylearchsecurity+2 | 73/100 | 3 days ago | |
| VictorGoic0/Math-Tutoring-App.cursor/rules/linting.mdc · 0 | Cursor rules | lint-formatstyledo-notagent-behaviour | 68/100 | 3 days ago | |
| VictorGoic0/Math-Tutoring-App.cursor/rules/one-pr-at-a-time.mdc · 0 | Cursor rules | git | 16/100 | 3 days ago | |
| VictorGoic0/Math-Tutoring-App.cursor/rules/react-patterns.mdc · 0 | Cursor rules | stylearchdo-not | 69/100 | 3 days ago |
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| hiromaily/go-crypto-wallet.cursor/rules/typescript.mdc · 126 | Cursor rules | setupbuildtestlint-format+6 | 100/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/10-hermes-api.mdc · 45 | Cursor rules | testlint-formatstylearch+5 | 100/100 | 3 days ago | |
| markstev/mark-starter.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+6 | 99/100 | 3 days ago | |
| Allymahmoud/case-intake-platform.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| dodgecfr/combatfilms-webapp.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| deifos/clipmira-subtitles.cursor/rules/frontend.mdc · 1 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| langflow-ai/langflow.cursor/rules/docs_development.mdc · 153k | Cursor rules | setupbuildtestlint-format+7 | 97/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/20-hermes-api-tests.mdc · 45 | Cursor rules | teststyletesting-strategysecurity+3 | 97/100 | 3 days ago |
