Skip to content

Commit ada2406

Browse files
authored
Merge pull request #2693 from rushi-tekdi/release-1.15.0
cursor rules with read me
2 parents 88b604e + b1d2e80 commit ada2406

File tree

2 files changed

+390
-0
lines changed

2 files changed

+390
-0
lines changed

.cursor/rules/README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Cursor Rules Directory
2+
3+
This directory contains rule files that guide AI code generation to follow best practices and avoid common pitfalls.
4+
5+
## Available Rules
6+
7+
### `react-state-management.md`
8+
**Purpose**: Prevents state leakage, infinite API calls, and complex useEffect chains in React components.
9+
10+
**When to reference**:
11+
- Creating new React components with state management
12+
- Working with useEffect hooks
13+
- Implementing API calls with debouncing
14+
- Managing complex state synchronization
15+
- Refactoring components with state issues
16+
17+
**Quick Reference Examples**:
18+
19+
1. **Creating new component**:
20+
```
21+
Create a filter component following react-state-management.md rules
22+
```
23+
24+
2. **Fixing state issues**:
25+
```
26+
Fix infinite loops in this component using react-state-management.md patterns
27+
```
28+
29+
3. **Specific rule**:
30+
```
31+
Apply rule #2 (useEffect Dependency Management) from react-state-management.md
32+
```
33+
34+
4. **Full reference**:
35+
```
36+
@react-state-management.md - Refactor this component
37+
```
38+
39+
## How Rules Work
40+
41+
- **Automatic**: Rules in this directory are automatically read by Cursor
42+
- **Explicit**: You can reference them in prompts using `@filename.md` or mentioning the rule name
43+
- **Combined**: Multiple rules work together automatically
44+
45+
## Best Practices
46+
47+
1. Reference rules at the start of your prompt for best results
48+
2. Be specific about which rules to apply
49+
3. Mention the rule file name when you want strict adherence
50+
4. Rules are additive - they work together with other project rules
51+
Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
# React State Management & useEffect Best Practices
2+
3+
## Critical Rules to Prevent State Leakage and Infinite API Calls
4+
5+
### 1. Minimize useRef Usage
6+
- **Rule**: Only use `useRef` for values that don't need to trigger re-renders (DOM refs, timeout IDs, stable callbacks)
7+
- **Anti-pattern**: Using multiple refs to track state changes (e.g., `isSelectingCenterRef`, `isFilterChangingRef`, `isSearchingRef`)
8+
- **Better approach**: Use state variables or derive from existing state
9+
- **Example Bad**:
10+
```tsx
11+
const isSelectingCenterRef = useRef(false);
12+
const isFilterChangingRef = useRef(false);
13+
const isSearchingRef = useRef(false);
14+
```
15+
- **Example Good**: Use state or derive from props/state directly
16+
17+
### 2. useEffect Dependency Management
18+
- **Rule**: Always include ALL dependencies that are used inside useEffect
19+
- **Rule**: If a function is used in useEffect, wrap it with `useCallback` and include it in dependencies
20+
- **Anti-pattern**: Using refs to store functions to avoid dependency issues
21+
```tsx
22+
// BAD: Storing function in ref to avoid dependencies
23+
const searchCentersRef = useRef(null);
24+
useEffect(() => {
25+
searchCentersRef.current = searchCenters;
26+
}, [searchCenters]);
27+
28+
useEffect(() => {
29+
searchCentersRef.current?.(); // Using ref instead of direct call
30+
}, [filters]);
31+
```
32+
- **Better approach**: Use `useCallback` and include in dependencies
33+
```tsx
34+
// GOOD: Stable function with useCallback
35+
const searchCenters = useCallback(async () => {
36+
// search logic
37+
}, [dependencies]);
38+
39+
useEffect(() => {
40+
searchCenters();
41+
}, [filters, searchCenters]); // Include the function
42+
```
43+
44+
### 3. Prevent Circular useEffect Chains
45+
- **Rule**: Never update state in useEffect that is also in its dependency array
46+
- **Anti-pattern**:
47+
```tsx
48+
useEffect(() => {
49+
setSelectedCenter(newCenters); // Updating selectedCenter
50+
}, [value, centerOptions, selectedCenter]); // selectedCenter is in deps!
51+
```
52+
- **Better approach**: Remove the state from dependencies or use a different pattern
53+
```tsx
54+
// GOOD: Only depend on external props/state
55+
useEffect(() => {
56+
if (value) {
57+
setSelectedCenter(computeCenters(value, centerOptions));
58+
}
59+
}, [value, centerOptions]); // Don't include selectedCenter
60+
```
61+
62+
### 4. Debouncing and Timeouts
63+
- **Rule**: Always clean up timeouts in useEffect cleanup
64+
- **Rule**: Use simple timeout logic, avoid complex ref tracking
65+
- **Anti-pattern**:
66+
```tsx
67+
const searchTimeoutRef = useRef(null);
68+
const lastFilterValuesRef = useRef({});
69+
70+
useEffect(() => {
71+
// Complex normalization and comparison
72+
const normalized = /* complex logic */;
73+
if (lastFilterValuesRef.current !== normalized) {
74+
if (searchTimeoutRef.current) clearTimeout(searchTimeoutRef.current);
75+
searchTimeoutRef.current = setTimeout(() => {
76+
// More complex checks with multiple refs
77+
if (!isSelectingCenterRef.current && !isFilterChangingRef.current) {
78+
searchCentersRef.current?.();
79+
}
80+
}, delay);
81+
}
82+
}, [dependencies]);
83+
```
84+
- **Better approach**: Simple debouncing with cleanup
85+
```tsx
86+
useEffect(() => {
87+
const timeoutId = setTimeout(() => {
88+
searchCenters();
89+
}, delay);
90+
91+
return () => clearTimeout(timeoutId);
92+
}, [dependencies, searchCenters]);
93+
```
94+
95+
### 5. State Synchronization Patterns
96+
- **Rule**: Avoid bidirectional state synchronization between props and internal state
97+
- **Rule**: Prefer controlled components pattern (props control state) or uncontrolled (internal state only)
98+
- **Anti-pattern**: Syncing `value` prop with `selectedCenter` state in both directions
99+
```tsx
100+
// BAD: Bidirectional sync causing loops
101+
useEffect(() => {
102+
// Sync value -> selectedCenter
103+
setSelectedCenter(computeFromValue(value));
104+
}, [value]);
105+
106+
const handleChange = (newValue) => {
107+
setSelectedCenter(newValue); // Update internal state
108+
onChange(newValue); // Update parent
109+
};
110+
```
111+
- **Better approach**: Single source of truth
112+
```tsx
113+
// GOOD: Use value prop as source of truth, or use internal state only
114+
const [selectedCenters, setSelectedCenters] = useState(selectedCenterList || []);
115+
116+
// Only sync from props if explicitly provided and different
117+
useEffect(() => {
118+
if (selectedCenterList && selectedCenterList !== selectedCenters) {
119+
setSelectedCenters(selectedCenterList);
120+
}
121+
}, [selectedCenterList]);
122+
```
123+
124+
### 6. API Call Optimization
125+
- **Rule**: Use `useCallback` for API functions to ensure stability
126+
- **Rule**: Include all dependencies in useCallback, don't use refs to bypass
127+
- **Rule**: Check conditions before making API calls, not with refs
128+
- **Anti-pattern**:
129+
```tsx
130+
const isSearchingRef = useRef(false);
131+
132+
const searchCenters = async () => {
133+
if (isSearchingRef.current) return; // Using ref to prevent calls
134+
isSearchingRef.current = true;
135+
// API call
136+
isSearchingRef.current = false;
137+
};
138+
```
139+
- **Better approach**: Use state or conditions
140+
```tsx
141+
const [isSearching, setIsSearching] = useState(false);
142+
143+
const searchCenters = useCallback(async () => {
144+
if (isSearching || !hasRequiredFilters) return;
145+
setIsSearching(true);
146+
try {
147+
// API call
148+
} finally {
149+
setIsSearching(false);
150+
}
151+
}, [isSearching, hasRequiredFilters]);
152+
```
153+
154+
### 7. Filter State Management
155+
- **Rule**: Keep filter state simple and avoid complex normalization
156+
- **Rule**: Don't track "last values" in refs to prevent duplicate calls
157+
- **Anti-pattern**:
158+
```tsx
159+
const lastFilterValuesRef = useRef({});
160+
161+
useEffect(() => {
162+
const normalized = /* complex normalization */;
163+
if (lastFilterValuesRef.current.state !== normalized.state) {
164+
lastFilterValuesRef.current = normalized;
165+
search();
166+
}
167+
}, [filters]);
168+
```
169+
- **Better approach**: React's dependency array handles this
170+
```tsx
171+
useEffect(() => {
172+
searchCenters();
173+
}, [selectedState, selectedDistrict, selectedBlock, searchKeyword, searchCenters]);
174+
// React automatically prevents duplicate calls if values haven't changed
175+
```
176+
177+
### 8. Handler Functions
178+
- **Rule**: Keep handlers simple, avoid setting multiple flags/refs
179+
- **Rule**: Update state directly, let useEffect handle side effects
180+
- **Anti-pattern**:
181+
```tsx
182+
const handleStateChange = (values) => {
183+
isFilterChangingRef.current = true; // Setting ref flag
184+
setSelectedState(values);
185+
setSelectedDistrict([]);
186+
setSelectedBlock([]);
187+
setCenterOptions([]);
188+
// Clear timeouts, update refs, etc.
189+
setTimeout(() => {
190+
isFilterChangingRef.current = false; // Reset flag
191+
}, 300);
192+
};
193+
```
194+
- **Better approach**: Simple state updates
195+
```tsx
196+
const handleStateChange = (values) => {
197+
setSelectedState(values);
198+
setSelectedDistrict([]);
199+
setSelectedBlock([]);
200+
// Let useEffect handle clearing options and searching
201+
};
202+
```
203+
204+
### 9. Component Lifecycle Management
205+
- **Rule**: Always use cleanup functions for async operations
206+
- **Rule**: Use `isMounted` flag for async operations, not refs for preventing calls
207+
- **Anti-pattern**:
208+
```tsx
209+
useEffect(() => {
210+
const fetchData = async () => {
211+
if (isSelectingCenterRef.current) return; // Using ref
212+
// fetch
213+
};
214+
}, []);
215+
```
216+
- **Better approach**:
217+
```tsx
218+
useEffect(() => {
219+
let isMounted = true;
220+
const fetchData = async () => {
221+
const data = await apiCall();
222+
if (isMounted) {
223+
setData(data);
224+
}
225+
};
226+
fetchData();
227+
return () => {
228+
isMounted = false;
229+
};
230+
}, []);
231+
```
232+
233+
### 10. Code Complexity Reduction
234+
- **Rule**: If you need more than 3-4 refs, reconsider the architecture
235+
- **Rule**: If useEffect has more than 5 dependencies, consider splitting or refactoring
236+
- **Rule**: If you need flags to prevent loops, the design needs improvement
237+
- **Rule**: Prefer multiple simple useEffects over one complex useEffect
238+
239+
## Summary Checklist
240+
241+
Before writing useEffect or state management code, ensure:
242+
243+
- [ ] All dependencies are included in useEffect dependency array
244+
- [ ] Functions used in useEffect are wrapped with useCallback
245+
- [ ] No state variable is both updated and in dependency array
246+
- [ ] Timeouts/intervals are cleaned up in useEffect cleanup
247+
- [ ] No more than 3-4 refs per component (excluding DOM refs)
248+
- [ ] No flags/refs used to prevent infinite loops (fix the root cause instead)
249+
- [ ] Simple debouncing without complex ref tracking
250+
- [ ] Single source of truth for state (props OR internal state, not both)
251+
- [ ] API calls are in useCallback with proper dependencies
252+
- [ ] Async operations check isMounted before setting state
253+
254+
## Example: Good vs Bad Patterns
255+
256+
### Bad Pattern (Causes State Leakage & Infinite Loops)
257+
```tsx
258+
// Too many refs
259+
const isSelectingCenterRef = useRef(false);
260+
const isFilterChangingRef = useRef(false);
261+
const isSearchingRef = useRef(false);
262+
const searchCentersRef = useRef(null);
263+
const lastFilterValuesRef = useRef({});
264+
265+
// Function stored in ref
266+
useEffect(() => {
267+
searchCentersRef.current = searchCenters;
268+
}, [searchCenters]);
269+
270+
// Complex useEffect with ref checks
271+
useEffect(() => {
272+
if (isSelectingCenterRef.current || isFilterChangingRef.current) return;
273+
const normalized = /* complex */;
274+
if (lastFilterValuesRef.current !== normalized) {
275+
searchTimeoutRef.current = setTimeout(() => {
276+
if (!isSearchingRef.current) {
277+
searchCentersRef.current?.();
278+
}
279+
}, delay);
280+
}
281+
}, [filters]);
282+
283+
// State in dependency array that's also updated
284+
useEffect(() => {
285+
setSelectedCenter(compute(value));
286+
}, [value, selectedCenter]); // selectedCenter updated inside!
287+
```
288+
289+
### Good Pattern (Clean & Stable)
290+
```tsx
291+
// Minimal refs (only for timeout cleanup)
292+
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
293+
294+
// Stable function with useCallback
295+
const searchCenters = useCallback(async () => {
296+
if (!hasRequiredFilters) return;
297+
setLoading(true);
298+
try {
299+
const data = await apiCall(filters);
300+
setCenters(data);
301+
} finally {
302+
setLoading(false);
303+
}
304+
}, [filters, hasRequiredFilters]);
305+
306+
// Simple debounced useEffect
307+
useEffect(() => {
308+
if (timeoutRef.current) {
309+
clearTimeout(timeoutRef.current);
310+
}
311+
312+
timeoutRef.current = setTimeout(() => {
313+
searchCenters();
314+
}, 500);
315+
316+
return () => {
317+
if (timeoutRef.current) {
318+
clearTimeout(timeoutRef.current);
319+
}
320+
};
321+
}, [selectedState, selectedDistrict, searchKeyword, searchCenters]);
322+
323+
// Simple state sync (one direction only)
324+
useEffect(() => {
325+
if (value && value !== selectedCenters.map(c => c.value)) {
326+
setSelectedCenters(computeFromValue(value));
327+
}
328+
}, [value]); // Don't include selectedCenters
329+
```
330+
331+
## Key Takeaways
332+
333+
1. **Less is More**: Fewer refs, simpler logic, cleaner code
334+
2. **Trust React**: React's dependency array handles change detection
335+
3. **useCallback is Your Friend**: Use it for functions in useEffect
336+
4. **Single Source of Truth**: Don't sync state bidirectionally
337+
5. **Fix Root Causes**: If you need flags to prevent loops, redesign
338+
6. **Keep it Simple**: Complex normalization and comparison usually indicates a design issue
339+

0 commit comments

Comments
 (0)