Skip to content

[jsx-no-constructed-context-values]: Wrongly(?) saying factory functions need to memoize an out of scope variable + comment wrongly identified as value that needs memoization #3116

Open
@AllySummers

Description

@AllySummers

Description
When using the rule (eslint-plugin-)react/jsx-no-constructed-context-values, it (wrongly?) reports a value from a factory function as a value that must be memoized. If you are also using (eslint-plugin-)react-hooks/exhaustive-deps and attempt to memoize the value to fix the lint error from jsx-no-constructed-context-values, the exhaustive-deps rule from react hooks will warn/error that out of scope variables are not valid dependencies.

I also discovered an additional issue while creating the repro.
If you put a prop in say a context provider with a comment, like so (also in below code): <MyContext.Provider value={{ /* some comment */ }}>, then this will be wrongly identified as a value that needs to be memoized (from jsx-no-constructed-context-values).

Repro
https://github.com/AllySummers/eslint-react-memo-factory
The issue can be seen from running npm run lint (in addition to using in-editor ESLint)

not-working.tsx

import React from 'react';

const UserContext = React.createContext({ user: null });

const UserContextProvider = ({ children }) => {
  return (
    <UserContext.Provider value={{ /* this is a comment */ }}> {/* Error: The object passed as the value prop to the Context provider (at line 7) changes every render. To fix this consider wrapping it in a useMemo hook. */}
      { children }
    </UserContext.Provider>
  )
};

const userFactoryWrapper1 = (user) => ({ children }) => (
  <UserContext.Provider value={{ user }}> {/* Error: The object passed as the value prop to the Context provider (at line 14) changes every render. To fix this consider wrapping it in a useMemo hook. */}
    { children }
  </UserContext.Provider>
)

const userFactoryWrapper2 = (user) => ({ children }) => {
  const memoizedValue = React.useMemo(() => ({
    user
  }), [user]); // Error: React Hook React.useMemo has an unnecessary dependency: 'user'. Either exclude it or remove the dependency array. Outer scope values like 'user' aren't valid dependencies because mutating them doesn't re-render the component.

  return (
    <UserContext.Provider value={memoizedValue}>
      { children }
    </UserContext.Provider>
  )
}

ESLint Config

{
  "root": true,
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": 12,
    "sourceType": "module"
  },
  "plugins": [
    "react",
    "react-hooks"
  ],
  "rules": {
    "react/jsx-no-constructed-context-values": "error",
    "react-hooks/exhaustive-deps": "warn"
  }
}

Expected Result

Typescript eslint should not issue a warning as the suggested code (as below)

Actual Result

The code provided in working.tsx is required due to how generics are parsed in Typescript/React, one cannot simply write <T>.

Additional Info
The attempted fix is invalid code:

export const GenericComponent = <T, = unknown>(props: GenericComponentProps<T>) => (
  <div>hello</div>
);

Versions

package version
node 14.16.0
eslint 7.32.0
eslint-plugin-react 7.26.1
eslint-plugin-react-hooks 4.2.0

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions