@@ -93,24 +93,26 @@ Once all violations in a file are fixed, the file's entry will be removed from `
9393
9494## Error reference
9595
96- | Compiler error message | Pattern | Section |
97- | ----------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------ |
98- | Cannot access refs during render | Ref access during render | [ Link] ( #ref-access-during-render ) |
99- | Existing memoization could not be preserved | Mismatched useMemo deps | [ Link] ( #mismatched-usememo-dependencies ) |
100- | Support destructuring of context variables | Mutating props | [ Link] ( #mutating-props ) |
101- | Expression type ` X ` cannot be safely reordered | Default parameters for props | [ Link] ( #default-parameters-for-props ) |
102- | Expression type ` BinaryExpression ` / ` LogicalExpression ` cannot be safely reordered | switch(true) pattern | [ Link] ( #switchtrue-pattern ) |
103- | Expected Identifier, got ` X ` key in ObjectExpression | Computed property keys | [ Link] ( #computed-property-keys ) |
104- | Destructure should never be Reassign | Loop variable reassignment | [ Link] ( #loop-variable-reassignment ) |
105- | Hooks must always be called in a consistent order | Conditional hook calls | [ Link] ( #conditional-hook-calls ) |
106- | Cannot access variable before it is declared | Function declaration order | [ Link] ( #function-declaration-order ) |
107- | Handle TryStatement with a finalizer / without a catch clause | Try/catch blocks | [ Link] ( #trycatch-blocks ) |
108- | ThrowStatement inside try/catch not yet supported | Try/catch blocks | [ Link] ( #trycatch-blocks ) |
109- | Support value blocks … within a try/catch statement | Try/catch blocks | [ Link] ( #trycatch-blocks ) |
110- | This value cannot be modified (hook argument) | Mutable objects with useMemo | [ Link] ( #mutable-objects-created-with-usememo ) |
111- | This value cannot be modified (function return) | Mutating return values | [ Link] ( #mutating-function-or-hook-return-values ) |
112- | This value cannot be modified (DOM) | DOM mutations | [ Link] ( #dom-mutations ) |
113- | This value cannot be modified (test code) | Render-time test mutations | [ Link] ( #render-time-mutations-in-test-code ) |
96+ | Compiler error message | Pattern | Section |
97+ | ----------------------------------------------------------------------------------- | ----------------------------- | ------------------------------------------------ |
98+ | Cannot access refs during render | Ref access during render | [ Link] ( #ref-access-during-render ) |
99+ | Existing memoization could not be preserved | Mismatched useMemo deps | [ Link] ( #mismatched-usememo-dependencies ) |
100+ | Support destructuring of context variables | Mutating props | [ Link] ( #mutating-props ) |
101+ | Expression type ` X ` cannot be safely reordered | Default parameters for props | [ Link] ( #default-parameters-for-props ) |
102+ | Expression type ` BinaryExpression ` / ` LogicalExpression ` cannot be safely reordered | switch(true) pattern | [ Link] ( #switchtrue-pattern ) |
103+ | Expected Identifier, got ` X ` key in ObjectExpression | Computed property keys | [ Link] ( #computed-property-keys ) |
104+ | Destructure should never be Reassign | Loop variable reassignment | [ Link] ( #loop-variable-reassignment ) |
105+ | Hooks must always be called in a consistent order | Conditional hook calls | [ Link] ( #conditional-hook-calls ) |
106+ | Hooks must be the same function on every render | Hooks passed as props | [ Link] ( #hooks-passed-as-props ) |
107+ | Hooks must be called at the top level … not within function expressions | Hooks in function expressions | [ Link] ( #hooks-in-function-expressions ) |
108+ | Cannot access variable before it is declared | Function declaration order | [ Link] ( #function-declaration-order ) |
109+ | Handle TryStatement with a finalizer / without a catch clause | Try/catch blocks | [ Link] ( #trycatch-blocks ) |
110+ | ThrowStatement inside try/catch not yet supported | Try/catch blocks | [ Link] ( #trycatch-blocks ) |
111+ | Support value blocks … within a try/catch statement | Try/catch blocks | [ Link] ( #trycatch-blocks ) |
112+ | This value cannot be modified (hook argument) | Mutable objects with useMemo | [ Link] ( #mutable-objects-created-with-usememo ) |
113+ | This value cannot be modified (function return) | Mutating return values | [ Link] ( #mutating-function-or-hook-return-values ) |
114+ | This value cannot be modified (DOM) | DOM mutations | [ Link] ( #dom-mutations ) |
115+ | This value cannot be modified (test code) | Render-time test mutations | [ Link] ( #render-time-mutations-in-test-code ) |
114116
115117## Fix patterns
116118
@@ -484,6 +486,99 @@ const currentPlacement = useStoreState(hovercardStore, 'currentPlacement')
484486const hovercardPlacement = currentPlacement ?.split (' -' )[0 ]
485487```
486488
489+ #### Hooks passed as props
490+
491+ > Reason: Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks
492+
493+ Passing a hook as a prop and calling it inside a child component is a violation — the hook identity can change between renders. Call the hook at the parent level and pass the result instead.
494+
495+ ** Before:**
496+
497+ ``` typescript
498+ function Parent() {
499+ const useValidator = useCallback (
500+ function useValidator() {
501+ return useFormValidator (formId )
502+ },
503+ [formId ],
504+ )
505+ return <FormField useValidator = {useValidator} />
506+ }
507+
508+ function FormField({ useValidator }: Props ) {
509+ const [state , validate ] = useValidator() // Violation
510+ // ...
511+ }
512+ ```
513+
514+ **After:**
515+
516+ ` ` ` typescript
517+ function Parent() {
518+ const validator = useFormValidator(formId)
519+ return <FormField validator={validator} />
520+ }
521+
522+ function FormField({ validator }: Props) {
523+ const [state, validate] = validator
524+ // ...
525+ }
526+ ` ` `
527+
528+ #### Hooks in function expressions
529+
530+ > Reason: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https : // react.dev/warnings/invalid-hook-call-warning)
531+
532+ Defining a component inside ` useMemo ` and calling hooks within it is a violation — the compiler sees hooks called inside a non -component function expression . Extract the component to the top level and use Context to pass dynamic values from the parent scope .
533+
534+ **Before : **
535+
536+ ` ` ` typescript
537+ function useCustomTrigger({ variant }: { variant: 'compact' | 'full' }) {
538+ return useMemo(
539+ () =>
540+ function CustomTrigger(props: TriggerProps) {
541+ const { items } = useListContext() // Violation
542+ return (
543+ <button {...props}>
544+ {variant === 'compact' ? items.length : items.join(', ')}
545+ </button>
546+ )
547+ },
548+ [variant],
549+ )
550+ }
551+
552+ function MySelect({ variant }: Props) {
553+ const CustomTrigger = useCustomTrigger({ variant })
554+ return <Select Trigger={CustomTrigger} />
555+ }
556+ ` ` `
557+
558+ **After : **
559+
560+ ` ` ` typescript
561+ const VariantContext = createContext<'compact' | 'full'>('full')
562+
563+ function CustomTrigger(props: TriggerProps) {
564+ const variant = useContext(VariantContext)
565+ const { items } = useListContext()
566+ return (
567+ <button {...props}>
568+ {variant === 'compact' ? items.length : items.join(', ')}
569+ </button>
570+ )
571+ }
572+
573+ function MySelect({ variant }: Props) {
574+ return (
575+ <VariantContext.Provider value={variant}>
576+ <Select Trigger={CustomTrigger} />
577+ </VariantContext.Provider>
578+ )
579+ }
580+ ` ` `
581+
487582### Function declaration order
488583
489584> Reason : Cannot access variable before it is declared
0 commit comments