eslint-plugin-react icon indicating copy to clipboard operation
eslint-plugin-react copied to clipboard

react/jsx-no-useless-fragment false positive

Open 3dos opened this issue 4 years ago • 14 comments

There's a false positive on the rule with forwarded children. Use cases are pretty rare but still exist. Without a fragment around the children, code breaks at runtime

const HollowComponent = ({ children }) => {
  // some logic here
  return <>{children}</>; // doesn't pass the rule
}

example: https://codesandbox.io/s/icy-lake-ri3uo

Temporary fix is to disable rule for this line but it would be nicer if this rule let this kind of Fragment use possible.

3dos avatar Mar 03 '20 10:03 3dos

What use cases would require the fragment?

ljharb avatar Mar 03 '20 19:03 ljharb

Here are 2 use cases I actually encounter in the project I'm working on.

1. Conditional rendering

Let's say we have a component augmenting its children conditionally like the following:

const Augmenter = ({ children }) => {
  // ... logic used here ...
  const tempJsx = (
    <>
      {additionalNodes}
      {children}
      {otherAdditionalNodes}
    </>
  )
  return renderWithAugmentations
    ? tempJsx
    : <>{children}</>
}

2. Testing hooks (mocks)

const MockComponentUseSomeHook = ({ children }) => {
  useSomeHook()
  return <>{children}</>
};

3dos avatar Mar 05 '20 09:03 3dos

In the first case, why wouldn't : children work identically?

Same question in the second case; return children should work fine.

ljharb avatar Mar 06 '20 21:03 ljharb

As you can see in the codesandbox I pasted in the original post ( https://codesandbox.io/s/suspicious-sunset-ly9ik ), returning the children without a fragment can throw a runtime error (in case no children are provided).

Working with typescript generates an error too as children type for a FunctionalComponent can be undefined and said functional component cannot return the undefined type.

3dos avatar Mar 06 '20 21:03 3dos

Thanks for bearing with me.

Type errors aren't relevant, but runtime errors are.

The solution in your code, of course, is to use defaultProps, or const TestEmpty = ({ children = null }) => children;, so that if it is undefined, it becomes null, or, const TestEmpty = ({ children }) => children ?? null;, but it seems reasonable to have an option to specifically allow a prop, only, to be used with an otherwise "unnecessary" fragment, because of undefined.

ljharb avatar Mar 06 '20 21:03 ljharb

Thank you for trying to understand the issue! I'll try the null workaround in my project which seems reasonable!

3dos avatar Mar 06 '20 23:03 3dos

Hi, I think I have encountered a similar problem. My use case is like this:

<Query>
  {({data, loading})  => {
    ...
    return (
      <>
        {anArray.map(item => {
          return item.success
            ? getTypeA(item)
            : getTypeB(item);
        })}
      </>
    );
  }
}
</Query>

This is treated as an error but the render prop expects the return to be a single element so I think the Fragment is necessary here.

zenefits-brody avatar May 11 '20 22:05 zenefits-brody

I have the same issue where I'm getting an error when there's a single map function inside the fragment. I can't enable this rule and use typescript at once because of this issue.

geoguide avatar Jul 14 '20 18:07 geoguide

Same error as zenefits-brody and geoguide.

There's only one child in program's AST, but there may be multiple children in React tree.

https://github.com/yannickcr/eslint-plugin-react/blob/master/lib/rules/jsx-no-useless-fragment.js#L106

One should check if the only child isn't jsx expression with a call expression inside.

Edit: Function call like .map can also return undefined so dropping the fragment in this case could result in runtime error.

hasparus avatar Aug 06 '20 14:08 hasparus

@ljharb @sbdchd Hi guys, thank you for your work!

Do we have any plan for when [New] jsx-no-useless-fragment: add ignoreUnsafeChildren flag Merge Request is going to be merged?

acherkashin avatar Sep 23 '21 05:09 acherkashin

(merge request is only a gitlab idiom, it's called a "pull request" most commonly)

That PR doesn't match https://github.com/yannickcr/eslint-plugin-react/issues/2584#issuecomment-595978999, so it needs to be updated to do so. If the PR author isn't interested in doing it, anyone else is welcome to comment a link to a branch (please do NOT open a new PR) and i'll update the PR with it.

ljharb avatar Sep 23 '21 05:09 ljharb

Had the same issue with conditional rendering did anyone find a solution for that? My case: return user ? <>{children}</> : <Navigate to={routes.login.path} />;

yarinsa avatar Dec 28 '21 10:12 yarinsa

Hi, I think I have encountered a similar problem. My use case is like this:

<Query>
  {({data, loading})  => {
    ...
    return (
      <>
        {anArray.map(item => {
          return item.success
            ? getTypeA(item)
            : getTypeB(item);
        })}
      </>
    );
  }
}
</Query>

This is treated as an error but the render prop expects the return to be a single element so I think the Fragment is necessary here.

Was this fixed already? I am still having the issue.

dejavu1987 avatar May 20 '22 14:05 dejavu1987

For me the solution was to add allowExpressions option to the config:

"react/jsx-no-useless-fragment": [2, { allowExpressions: true }]

przemke avatar Jul 21 '22 08:07 przemke

allowExpressions: true is not work for me

batazo avatar May 06 '23 14:05 batazo