feat: Qoder rule (#70)

Co-authored-by: kyle.jiang@dbappsecurity.com.cn <kyle.jiang@dbappsecurity.com.cn>
This commit is contained in:
xiaocaiji
2026-01-16 09:05:16 +08:00
committed by GitHub
parent 408df90766
commit 1d5659bd77
20 changed files with 1298 additions and 0 deletions

60
.qoder/rules/react.md Normal file
View File

@@ -0,0 +1,60 @@
---
trigger: glob
glob: src/*tsx,src/*jsx
---
| No | Category | Guideline | Description | Do | Don't | Code Good | Code Bad | Severity | Docs URL |
| --- | ------------- | ----------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------- | ----------------------------------------------- | ------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------- | --- |
| 1 | State | Use useState for local state | Simple component state should use useState hook | useState for form inputs, toggles, counters | Class components this.state | `const [count, setCount] = useState(0)` | `this.state = { count: 0 }` | Medium | [useState](https://react.dev/reference/react/useState) |
| 2 | State | Lift state up when needed | Share state between siblings by lifting to parent | Lift shared state to common ancestor | Prop drilling through many levels | Parent holds state, passes down | Deep prop chains | Medium | [Sharing State](https://react.dev/learn/sharing-state-between-components) |
| 3 | State | Use useReducer for complex state | Complex state logic benefits from reducer pattern | useReducer for state with multiple sub-values | Multiple useState for related values | useReducer with action types | 5+ useState calls that update together | Medium | [useReducer](https://react.dev/reference/react/useReducer) |
| 4 | State | Avoid unnecessary state | Derive values from existing state when possible | Compute derived values in render | Store derivable values in state | `const total = items.reduce(...)` | `const [total, setTotal] = useState(0)` | High | [Choosing State Structure](https://react.dev/learn/choosing-the-state-structure) |
| 5 | State | Initialize state lazily | Use function form for expensive initial state | useState(() => computeExpensive()) | useState(computeExpensive()) | useState(() => JSON.parse(data)) | useState(JSON.parse(data)) | Medium | [Avoiding Recreating Initial State](https://react.dev/reference/react/useState#avoiding-recreating-the-initial-state) |
| 6 | Effects | Clean up effects | Return cleanup function for subscriptions, timers | Return cleanup function in useEffect | No cleanup for subscriptions | `useEffect(() => { sub(); return unsub; })` | `useEffect(() => { subscribe(); })` | High | [Connecting to External Systems](https://react.dev/reference/react/useEffect#connecting-to-an-external-system) |
| 7 | Effects | Specify dependencies correctly | Include all values used inside effect in deps array | All referenced values in dependency array | Empty deps with external references | `[value] when using value in effect` | `[] when using props/state in effect` | High | [Specifying Reactive Dependencies](https://react.dev/reference/react/useEffect#specifying-reactive-dependencies) |
| 8 | Effects | Avoid unnecessary effects | Don't use effects for transforming data or events | Transform data during render, handle events directly | useEffect for derived state or event handling | `const filtered = items.filter(...)` | `useEffect(() => setFiltered(items.filter(...)))` | High | [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect) |
| 9 | Effects | Use refs for non-reactive values | Store values that don't trigger re-renders in refs | useRef for interval IDs, DOM elements | useState for values that don't need render | `const intervalRef = useRef(null)` | `const [intervalId, setIntervalId] = useState()` | Medium | [useRef](https://react.dev/reference/react/useRef) |
| 10 | Rendering | Use keys properly | Stable unique keys for list items | Use stable IDs as keys | Array index as key for dynamic lists | `key={item.id}` | `key={index}` | High | [Keeping List Items in Order](https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key) |
| 11 | Rendering | Memoize expensive calculations | Use useMemo for costly computations | useMemo for expensive filtering/sorting | Recalculate every render | `useMemo(() => expensive(), [deps])` | `const result = expensiveCalc()` | Medium | [useMemo](https://react.dev/reference/react/useMemo) |
| 12 | Rendering | Memoize callbacks passed to children | Use useCallback for functions passed as props | useCallback for handlers passed to memoized children | New function reference every render | `useCallback(() => {}, [deps])` | `const handler = () => {}` | Medium | [useCallback](https://react.dev/reference/react/useCallback) |
| 13 | Rendering | Use React.memo wisely | Wrap components that render often with same props | memo for pure components with stable props | memo everything or nothing | `memo(ExpensiveList)` | `memo(SimpleButton)` | Low | [React.memo](https://react.dev/reference/react/memo) |
| 14 | Rendering | Avoid inline object/array creation in JSX | Create objects outside render or memoize | Define style objects outside component | Inline objects in props | `<div style={styles.container}>` | `<div style={{ margin: 10 }}>` | Medium | |
| 15 | Components | Keep components small and focused | Single responsibility for each component | One concern per component | Large multi-purpose components | `<UserAvatar /><UserName />` | `<UserCard />` with 500 lines | Medium | |
| 16 | Components | Use composition over inheritance | Compose components using children and props | Use children prop for flexibility | Inheritance hierarchies | `<Card>{content}</Card>` | `class SpecialCard extends Card` | Medium | [Thinking in React](https://react.dev/learn/thinking-in-react) |
| 17 | Components | Colocate related code | Keep related components and hooks together | Related files in same directory | Flat structure with many files | `components/User/UserCard.tsx` | `components/UserCard.tsx` + `hooks/useUser.ts` | Low | |
| 18 | Components | Use fragments to avoid extra DOM | Fragment or <> for multiple elements without wrapper | <> for grouping without DOM node | Extra div wrappers | `<>` for grouping without DOM node | `<div>{items.map(...)}</div>` | Low | [React.Fragment](https://react.dev/reference/react/Fragment) |
| 19 | Props | Destructure props | Destructure props for cleaner component code | Destructure in function signature | props.name props.value throughout | `function User({ name, age })` | `function User(props)` | Low | |
| 20 | Props | Provide default props values | Use default parameters or defaultProps | Default values in destructuring | Undefined checks throughout | `function Button({ size = 'md' })` | `if (size === undefined) size = 'md'` | Low | |
| 21 | Props | Avoid prop drilling | Use context or composition for deeply nested data | Context for global data, composition for UI | Passing props through 5+ levels | `<UserContext.Provider>` | `<A user={u}><B user={u}><C user={u}>` | Medium | [Passing Data Deeply](https://react.dev/learn/passing-data-deeply-with-context) |
| 22 | Props | Validate props with TypeScript | Use TypeScript interfaces for prop types | interface Props { name: string } | PropTypes or no validation | `interface ButtonProps { onClick: () => void }` | `Button.propTypes = {}` | Medium | |
| 23 | Events | Use synthetic events correctly | React normalizes events across browsers | e.preventDefault() e.stopPropagation() | Access native event unnecessarily | `onClick={(e) => e.preventDefault()}` | `onClick={(e) => e.nativeEvent.preventDefault()}` | Low | [React Event Object](https://react.dev/reference/react-dom/components/common#react-event-object) |
| 24 | Events | Avoid binding in render | Use arrow functions in class or hooks | Arrow functions in functional components | bind in render or constructor | `const handleClick = () => {}` | `this.handleClick.bind(this)` | Medium | |
| 25 | Events | Pass event handlers not call results | Pass function reference not invocation | onClick={handleClick} | onClick={handleClick()} causing immediate call | `onClick={handleClick}` | `onClick={handleClick()}` | High | |
| 26 | Forms | Controlled components for forms | Use state to control form inputs | value + onChange for inputs | Uncontrolled inputs with refs | `<input value={val} onChange={setVal}>` | `<input ref={inputRef}>` | Medium | [Controlling an Input](https://react.dev/reference/react-dom/components/input#controlling-an-input-with-a-state-variable) |
| 27 | Forms | Handle form submission properly | Prevent default and handle in submit handler | onSubmit with preventDefault | onClick on submit button only | `<form onSubmit={handleSubmit}>` | `<button onClick={handleSubmit}>` | Medium | |
| 28 | Forms | Debounce rapid input changes | Debounce search/filter inputs | useDeferredValue or debounce for search | Filter on every keystroke | `useDeferredValue(searchTerm)` | `useEffect filtering on every change` | Medium | [useDeferredValue](https://react.dev/reference/react/useDeferredValue) |
| 29 | Hooks | Follow rules of hooks | Only call hooks at top level and in React functions | Hooks at component top level | Hooks in conditions, loops, or callbacks | `const [x, setX] = useState()` | `if (cond) { const [x, setX] = useState() }` | High | [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks) |
| 30 | Hooks | Custom hooks for reusable logic | Extract shared stateful logic to custom hooks | useCustomHook for reusable patterns | Duplicate hook logic across components | `const { data } = useFetch(url)` | Duplicate useEffect/useState in components | Medium | [Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) |
| 31 | Hooks | Name custom hooks with use prefix | Custom hooks must start with use | useFetch useForm useAuth | fetchData or getData for hook | `function useFetch(url)` | `function fetchData(url)` | High | |
| 32 | Context | Use context for global data | Context for theme, auth, locale | Context for app-wide state | Context for frequently changing data | `<ThemeContext.Provider>` | Context for form field values | Medium | [Passing Data Deeply](https://react.dev/learn/passing-data-deeply-with-context) |
| 33 | Context | Split contexts by concern | Separate contexts for different domains | ThemeContext + AuthContext | One giant AppContext | `<ThemeProvider><AuthProvider>` | `<AppProvider value={{theme, user...}}>` | Medium | |
| 34 | Context | Memoize context values | Prevent unnecessary re-renders with useMemo | useMemo for context value object | New object reference every render | `value={useMemo(() => ({...}), [])}` | `value={{ user, theme }}` | High | |
| 35 | Performance | Use React DevTools Profiler | Profile to identify performance bottlenecks | Profile before optimizing | Optimize without measuring | React DevTools Profiler | Guessing at bottlenecks | Medium | [React Developer Tools](https://react.dev/learn/react-developer-tools) |
| 36 | Performance | Lazy load components | Use React.lazy for code splitting | lazy() for routes and heavy components | Import everything upfront | `const Page = lazy(() => import('./Page'))` | `import Page from './Page'` | Medium | [React.lazy](https://react.dev/reference/react/lazy) |
| 37 | Performance | Virtualize long lists | Use windowing for lists over 100 items | react-window or react-virtual | Render thousands of DOM nodes | `<VirtualizedList items={items}/>` | `{items.map(i => <Item />)` | High | |
| 38 | Performance | Batch state updates | React 18 auto-batches but be aware | Let React batch related updates | Manual batching with flushSync | `setA(1); setB(2); // batched` | `flushSync(() => setA(1))` | Low | [Queueing a Series of State Updates](https://react.dev/learn/queueing-a-series-of-state-updates) |
| 39 | ErrorHandling | Use error boundaries | Catch JavaScript errors in component tree | ErrorBoundary wrapping sections | Let errors crash entire app | `<ErrorBoundary><App/></ErrorBoundary>` | No error handling | High | [Error Boundaries](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) |
| 40 | ErrorHandling | Handle async errors | Catch errors in async operations | try/catch in async handlers | Unhandled promise rejections | `try { await fetch() } catch(e) {}` | `await fetch() // no catch` | High | |
| 41 | Testing | Test behavior not implementation | Test what user sees and does | Test renders and interactions | Test internal state or methods | `expect(screen.getByText('Hello'))` | `expect(component.state.name)` | Medium | [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) |
| 42 | Testing | Use testing-library queries | Use accessible queries | getByRole getByLabelText | getByTestId for everything | `getByRole('button')` | `getByTestId('submit-btn')` | Medium | [Queries](https://testing-library.com/docs/queries/about#priority) |
| 43 | Accessibility | Use semantic HTML | Proper HTML elements for their purpose | button for clicks, nav for navigation | div with onClick for buttons | `<button onClick={...}>` | `<div onClick={...}>` | High | [React DOM Components](https://react.dev/reference/react-dom/components#all-html-components) |
| 44 | Accessibility | Manage focus properly | Handle focus for modals, dialogs | Focus trap in modals, return focus on close | No focus management | `useEffect to focus input` | Modal without focus trap | High | |
| 45 | Accessibility | Announce dynamic content | Use ARIA live regions for updates | aria-live for dynamic updates | Silent updates to screen readers | `<div aria-live="polite">{msg}</div>` | `<div>{msg}</div>` | Medium | |
| 46 | Accessibility | Label form controls | Associate labels with inputs | htmlFor matching input id | Placeholder as only label | `<label htmlFor="email">Email</label>` | `<input placeholder="Email"/>` | High | |
| 47 | TypeScript | Type component props | Define interfaces for all props | interface Props with all prop types | any or missing types | `interface Props { name: string }` | `function Component(props: any)` | High | |
| 48 | TypeScript | Type state properly | Provide types for useState | useState<Type>() for complex state | Inferred any types | `useState<User | null>(null)` | `useState(null)` | Medium | |
| 49 | TypeScript | Type event handlers | Use React event types | React.ChangeEvent<HTMLInputElement> | Generic Event type | `onChange: React.ChangeEvent<HTMLInputElement>` | `onChange: Event` | Medium | |
| 50 | TypeScript | Use generics for reusable components | Generic components for flexible typing | Generic props for list components | Union types for flexibility | `<List<T> items={T[]}>` | `<List items={any[]}>` | Medium | |
| 51 | Patterns | Container/Presentational split | Separate data logic from UI | Container fetches, presentational renders | Mixed data and UI in one | `<UserContainer><UserView/></UserContainer>` | `<User />` with fetch and render | Low | |
| 52 | Patterns | Render props for flexibility | Share code via render prop pattern | Render prop for customizable rendering | Duplicate logic across components | `<DataFetcher render={data => ...}/> ` | Copy paste fetch logic | Low | [Render Props](https://react.dev/reference/react/cloneElement#passing-data-with-a-render-prop) |
| 53 | Patterns | Compound components | Related components sharing state | Tab + TabPanel sharing context | Prop drilling between related | `<Tabs><Tab/><TabPanel/></Tabs>` | `<Tabs tabs={[]} panels={[...]}/> ` | Low | |