{"record":{"id":"e177bc3af8bd1887","repo":"facebook/react","slug":"unsupported-node-type-expression-type","errorCode":null,"errorMessage":"Unsupported node type: ${expression.type}","messagePattern":"Unsupported node type: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts","lineNumber":1957,"sourceCode":"    const object = analyzePropertyChain(node.object, optionalChains);\n    const property = analyzePropertyChain(node.property, null);\n    const result = `${object}.${property}`;\n    markNode(node, optionalChains, result);\n    return result;\n  } else if (node.type === 'OptionalMemberExpression' && !node.computed) {\n    const object = analyzePropertyChain(node.object, optionalChains);\n    const property = analyzePropertyChain(node.property, null);\n    const result = `${object}.${property}`;\n    markNode(node, optionalChains, result);\n    return result;\n  } else if (\n    node.type === 'ChainExpression' &&\n    (!('computed' in node) || !node.computed)\n  ) {\n    const expression = node.expression;\n\n    if (expression.type === 'CallExpression') {\n      throw new Error(`Unsupported node type: ${expression.type}`);\n    }\n\n    const object = analyzePropertyChain(expression.object, optionalChains);\n    const property = analyzePropertyChain(expression.property, null);\n    const result = `${object}.${property}`;\n    markNode(expression, optionalChains, result);\n    return result;\n  } else {\n    throw new Error(`Unsupported node type: ${node.type}`);\n  }\n}\n\nfunction getNodeWithoutReactNamespace(\n  node: Expression | Super,\n): Expression | Identifier | Super {\n  if (\n    node.type === 'MemberExpression' &&\n    node.object.type === 'Identifier' &&","sourceCodeStart":1939,"sourceCodeEnd":1975,"githubUrl":"https://github.com/facebook/react/blob/eafeac097ba51e1eab809c07102126bd5f8e5425/packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts#L1939-L1975","documentation":"While building the property-chain strings it uses to track stable values, the exhaustive-deps rule walks each dependency expression with analyzePropertyChain. Identifiers and non-computed member/optional-member chains map to strings like 'a.b.c', but a ChainExpression that wraps a CallExpression (an optional call such as foo?.()) has no property-chain representation, so the analysis throws. In practice this means a dependency entry containing an optional function invocation crashes the rule.","triggerScenarios":"A dependency array entry that is an optional call: useEffect(() => {...}, [props.getValue?.()]), useMemo(..., [ref.current?.()]), or chains ending in a call like a?.b.c(). The ChainExpression branch accepts only member-style chains; the CallExpression inside it hits the explicit throw.","commonSituations":"Codebases using optional chaining heavily; TypeScript strict mode where a function prop is possibly undefined; refactoring that moved an optional invocation from the callback body into the deps array; older plugin versions before optional-chain handling matured.","solutions":["Update eslint-plugin-react-hooks - newer versions handle more optional-chain shapes without crashing","Depend on the function reference itself and invoke it inside the callback: list props.getValue and call props.getValue?.() in the effect body","Hoist the result to a variable before the hook if the value is already computed, and depend on that variable","As a last resort, add // eslint-disable-next-line react-hooks/exhaustive-deps with a comment explaining why the expression cannot be listed"],"exampleFix":"// before\nuseEffect(() => {\n  props.onReady?.();\n}, [props.onReady?.()]);\n\n// after\nconst onReady = props.onReady;\nuseEffect(() => {\n  onReady?.();\n}, [onReady]);","handlingStrategy":"validation","validationCode":"// Pre-flight scan: flag optional calls in dependency arrays before lint runs\n// (cheap grep-style check usable in CI or a pre-commit hook)\nconst optionalCallInDeps = /\\[[^\\]]*?\\?\\.[^\\]]*?\\(/.test(sourceOfHookFile);\nif (optionalCallInDeps) {\n  console.warn('Optional call inside a deps array - rewrite as fn reference + fn?.() in the callback');\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never list an invocation in the deps array - list the function/value being called","Call optional functions inside the callback body with fn?.(), depending on fn","Run lint locally on hook-heavy files before committing","Upgrade eslint-plugin-react-hooks when adopting newer syntax forms"],"tags":["eslint","react-hooks","optional-chaining","static-analysis","dependency-array"],"backgroundTag":"unsupported-ast-node","analyzedSha":"eafeac097ba51e1eab809c07102126bd5f8e5425","analyzedAt":"2026-08-21T22:01:08.818Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}