Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: False positive for react/no-unused-prop-types and passthrough #3885

Open
2 tasks done
schulzjo-tng opened this issue Feb 11, 2025 · 4 comments
Open
2 tasks done
Labels

Comments

@schulzjo-tng
Copy link

Is there an existing issue for this?

  • I have searched the existing issues and my issue is unique
  • My issue appears in the command-line and not only in the text editor

Description Overview

The following code causes a false positive on react/no-unused-prop-types:

export type TestProp = {
    name: string;
};

const Component1: FC<TestProp> = ({ name }) => {
    return <div>{name}</div>;
};

const Component2: FC<{ Component: FC<TestProp>; props: TestProp }> = ({ Component, props }) => {
    return <Component name={props.name} />;
};

const Component3: FC<TestProp> = (props: TestProp) => {
    return <Component2 Component={Component1} props={props} />;
};

Error: 'name' PropType is defined but prop is never used.

The error goes away when commenting out Component3.

Expected Behavior

The code above should be accepted by the linter.

eslint-plugin-react version

v7.37.4

eslint version

v8.57.1

node version

v20.15.0

@ljharb
Copy link
Member

ljharb commented Feb 11, 2025

This is a very strange pattern - why pass a component and a props object around instead of just passing an element directly?

Either way, you’re meant to access all of the props individually in each component in which it’s defined, so if you want to write your code this way, then you may need go use an override comment in Component3.

@schulzjo-tng
Copy link
Author

schulzjo-tng commented Feb 11, 2025

I didn't include the whole complexity here to keep this example minimal. In the use case, Component2 is rather complex and generates additional properties that are fed into Component1. Now we have ~10 components that can be used for Component1, and each of these should receive an abstraction in the form of Component3.

I would be happy to suppress this warning via // eslint-disable-next-line at Component3, but that does not fix the issue since the problem is raised in line 2 at name: string;. I can only suppress the warning for the whole file or line by line in the type definition. In either case, I lose the ability to check whether all properties are used in Component2 (which works just fine when Component3 is disabled).

@ljharb
Copy link
Member

ljharb commented Mar 25, 2025

Unfortunately the minimal example doesn't really justify or explain the (uncommon and bizarre) approach - can you provide something a bit more thorough so I can understand why this is a reasonable way to architect your components?

@schulzjo-tng
Copy link
Author

Here are some additional details on the setup. I cannot show any of the real code, though, since it belongs to a client.

  • The client uses a popular frontend table library.
  • This frontend library is extended by a custom UI for the filters.
  • There are different types of custom filters with different arguments.
  • They share a lot of setup code (e.g. to make them "float").

Roughly speaking, the code looks like this (I am skipping some of the typing here):

type InnerFilterProps = { 
    name: string;
    computedProperty: /* ... */
 };

const CustomFilter: FC<{ Component: FC<InnerFilterProps>; innerProps: InnerFilterProps }> = ({ Component, innerProps, ...customFilterProps }) => {
    // complex setup
    
    return (
        <outerComponents>
            <Component name={props.name} computedProperty={somethingComputedInTheSetup} />
        </outerComponents>;
    )
};

Now, there are filters declared like

const InnerFilterString: FC<InnerFilterProps & StringFilterProps> = (/*...*/) => { /* ... */ };

const CustomFilterString: FC<InnerFilterProps & StringFilterProps> = (props) => {
    return <CustomFilter Component={InnerFilterString} props = {props}/>
}

const InnerFilterInteger = /* ... */
// ... more of those

Finally, the call in the table library looks roughly like

const columns = [
   {
       // ...
       filter: {
            Component: CustomFilterString,
            customProps: { /* ... */ }
       }
   }
]
<LibraryTable columns={columns}/>

The complexity has multiple reasons:

  • We have no control over the interface of the library where we insert our custom component.
  • The setup code in CustomFilter is rather complex and we do not want to duplicate it multiple times.
  • Some of the parameters to the inner filter are computed in the CustomFilter.
  • There are also some generics involved which I skipped over here, making it infeasible to disassemble and reassemble the properties in all places.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

2 participants