Repository: facebook/react
Pull request: [compiler] Fix dropped ref with spread props in InlineJsxTransform

When supporting ref as prop in https://github.com/facebook/react/pull/31558, I missed fixing the optimization to pass a spread-props-only props object in without an additional object copy. In the case that we have only a ref along with a spread, we cannot return only the spread object. This results in dropping the ref.

In this example
```javascript
<Foo ref={ref} {...props} />
```

The bugged output is:
```javascript
{
  // ...
  props: props
}
```

With this change we now get the correct output:
```javascript
{
  // ...
  props: {ref: ref, ...props}
}
```

Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts
patch: @@ -546,16 +546,14 @@ function createPropsProperties(
   let refProperty: ObjectProperty | undefined;
   let keyProperty: ObjectProperty | undefined;
   const props: Array<ObjectProperty | SpreadPattern> = [];
-  const jsxAttributesWithoutKeyAndRef = propAttributes.filter(
-    p => p.kind === 'JsxAttribute' && p.name !== 'key' && p.name !== 'ref',
+  const jsxAttributesWithoutKey = propAttributes.filter(
+    p => p.kind === 'JsxAttribute' && p.name !== 'key',
   );
   const jsxSpreadAttributes = propAttributes.filter(
     p => p.kind === 'JsxSpreadAttribute',
   );
   const spreadPropsOnly =
-    jsxAttributesWithoutKeyAndRef.length === 0 &&
-    jsxSpreadAttributes.length === 1;
-
+    jsxAttributesWithoutKey.length === 0 && jsxSpreadAttributes.length === 1;
   propAttributes.forEach(prop => {
     switch (prop.kind) {
       case 'JsxAttribute': {

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md
patch: @@ -60,6 +60,10 @@ function ConditionalJsx({shouldWrap}) {
   return content;
 }
 
+function ComponentWithSpreadPropsAndRef({ref, ...other}) {
+  return <Foo ref={ref} {...other} />;
+}
+
 // TODO: Support value blocks
 function TernaryJsx({cond}) {
   return cond ? <div /> : null;
@@ -409,6 +413,41 @@ function ConditionalJsx(t0) {
   return content;
 }
 
+function ComponentWithSpreadPropsAndRef(t0) {
+  const $ = _c2(6);
+  let other;
+  let ref;
+  if ($[0] !== t0) {
+    ({ ref, ...other } = t0);
+    $[0] = t0;
+    $[1] = other;
+    $[2] = ref;
+  } else {
+    other = $[1];
+    ref = $[2];
+  }
+  let t1;
+  if ($[3] !== other || $[4] !== ref) {
+    if (DEV) {
+      t1 = <Foo ref={ref} {...other} />;
+    } else {
+      t1 = {
+        $$typeof: Symbol.for("react.transitional.element"),
+        type: Foo,
+        ref: ref,
+        key: null,
+        props: { ref: ref, ...other },
+      };
+    }
+    $[3] = other;
+    $[4] = ref;
+    $[5] = t1;
+  } else {
+    t1 = $[5];
+  }
+  return t1;
+}
+
 // TODO: Support value blocks
 function TernaryJsx(t0) {
   const $ = _c2(2);

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js
patch: @@ -56,6 +56,10 @@ function ConditionalJsx({shouldWrap}) {
   return content;
 }
 
+function ComponentWithSpreadPropsAndRef({ref, ...other}) {
+  return <Foo ref={ref} {...other} />;
+}
+
 // TODO: Support value blocks
 function TernaryJsx({cond}) {
   return cond ? <div /> : null;