Fix 'Expected an Assignment or Function Call' in ESLint
How-to guide for fixing ESLint's no-unused-expressions error with four common causes and code fixes.
Fix ESLint 'Expected an assignment or function call and instead saw an expression'
\nThis warning comes from ESLint's no-unused-expressions rule. It fires when a statement evaluates a value but never uses it, meaning the line probably does nothing at runtime. Below are the four causes that account for almost every real-world hit, each with a minimal reproduction and the corrected code.
1. Arrow function returning an object literal without parentheses
\nThis is by far the most common cause, especially in React and Redux code. Concise arrow bodies interpret { ... } as a function body, not an object. Property keys like a: are then parsed as labels, and the value expression is unused.
// Broken: parsed as a block with label 'name', string is unused\nconst mapUser = user => { name: user.name, id: user.id };\n\n// Fixed: parentheses force object-literal parsing\nconst mapUser = user => ({ name: user.name, id: user.id });\nThe same trap hits Redux selectors and RTK Query transformers:
\n// Broken\nconst selectVisible = state => { items: state.items.filter(i => i.visible) };\n\n// Fixed\nconst selectVisible = state => ({ items: state.items.filter(i => i.visible) });\n\n2. Ternary expression used only for side effects
\nTernaries return a value. When you write one at statement level to do two things, the resulting value is discarded and ESLint complains. Use if/else instead.
// Broken\nuser.isAdmin ? grantAccess(user) : denyAccess(user);\n\n// Fixed\nif (user.isAdmin) {\n grantAccess(user);\n} else {\n denyAccess(user);\n}\nIf you really want an expression, assign or return the result:
\nconst result = user.isAdmin ? grantAccess(user) : denyAccess(user);\n\n3. Short-circuit expression that never calls anything
\nThe pattern condition && doSomething() is idiomatic, but only when doSomething() is actually invoked. Forgetting the parentheses turns it into a plain value lookup with no effect.
// Broken: reads the property but never calls it\nisReady && onReady;\n\n// Fixed\nisReady && onReady();\nThe same pattern with optional chaining bites teams migrating older code:
\n// Broken\nuser?.logout;\n\n// Fixed\nuser?.logout();\nIf the right side is not a callable, prefer an explicit statement:
\nif (isReady) status = 'done';\n\n4. Forgetting parentheses on a function call
\nReferencing a function without invoking it evaluates to the function object and then throws it away.
\n// Broken\nfunction init() {\n setup; // reference, not a call\n render; // reference, not a call\n}\n\n// Fixed\nfunction init() {\n setup();\n render();\n}\nThe same happens with method calls copied from documentation:
\n// Broken\nresponse.json;\n\n// Fixed\nconst data = await response.json();\n\nVerifying the fix
\nAfter changing the line, re-run ESLint against just that file to confirm:
\nnpx eslint src/components/UserCard.jsx\nIf the rule still fires, the statement almost certainly still evaluates to a discarded value. Read it aloud as \"take this value and throw it away\" — that framing makes the problem obvious.
\n\nWhen to disable the rule
\nOccasionally you have a legitimate expression statement, such as a Chai assertion (expect(x).to.be.true) or a directive prologue. Configure exceptions rather than disabling the rule globally:
// .eslintrc.js\nrules: {\n 'no-unused-expressions': ['error', {\n allowShortCircuit: true,\n allowTernary: true,\n allowTaggedTemplates: true,\n }],\n}\nFor Chai specifically, install eslint-plugin-chai-friendly so the rule ignores assertion chains without turning off protection for the rest of your codebase.