Skip to content

Commit bc70441

Browse files
authored
RFC #30: React.forwardRef implementation (#12346)
Added React.forwardRef support to react-reconciler based renders and the SSR partial renderer.
1 parent 7719610 commit bc70441

13 files changed

+368
-2
lines changed

packages/react-dom/src/__tests__/ReactDOMServerIntegrationRefs-test.js

+21
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,25 @@ describe('ReactDOMServerIntegration', () => {
9696
expect(component.refs.myDiv).toBe(root.firstChild);
9797
});
9898
});
99+
100+
it('should forward refs', async () => {
101+
const divRef = React.createRef();
102+
103+
class InnerComponent extends React.Component {
104+
render() {
105+
return <div ref={this.props.forwardedRef}>{this.props.value}</div>;
106+
}
107+
}
108+
109+
const OuterComponent = React.forwardRef((props, ref) => (
110+
<InnerComponent {...props} forwardedRef={ref} />
111+
));
112+
113+
await clientRenderOnServerString(
114+
<OuterComponent ref={divRef} value="hello" />,
115+
);
116+
117+
expect(divRef.current).not.toBe(null);
118+
expect(divRef.current.textContent).toBe('hello');
119+
});
99120
});

packages/react-dom/src/server/ReactPartialRenderer.js

+20
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import describeComponentFrame from 'shared/describeComponentFrame';
2727
import {ReactDebugCurrentFrame} from 'shared/ReactGlobalSharedState';
2828
import {warnAboutDeprecatedLifecycles} from 'shared/ReactFeatureFlags';
2929
import {
30+
REACT_FORWARD_REF_TYPE,
3031
REACT_FRAGMENT_TYPE,
3132
REACT_STRICT_MODE_TYPE,
3233
REACT_ASYNC_MODE_TYPE,
@@ -841,6 +842,25 @@ class ReactDOMServerRenderer {
841842
}
842843
if (typeof elementType === 'object' && elementType !== null) {
843844
switch (elementType.$$typeof) {
845+
case REACT_FORWARD_REF_TYPE: {
846+
const element: ReactElement = ((nextChild: any): ReactElement);
847+
const nextChildren = toArray(
848+
elementType.render(element.props, element.ref),
849+
);
850+
const frame: Frame = {
851+
type: null,
852+
domNamespace: parentNamespace,
853+
children: nextChildren,
854+
childIndex: 0,
855+
context: context,
856+
footer: '',
857+
};
858+
if (__DEV__) {
859+
((frame: any): FrameDev).debugElementStack = [];
860+
}
861+
this.stack.push(frame);
862+
return '';
863+
}
844864
case REACT_PROVIDER_TYPE: {
845865
const provider: ReactProvider<any> = (nextChild: any);
846866
const nextProps = provider.props;

packages/react-is/src/ReactIs.js

+6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
REACT_ASYNC_MODE_TYPE,
1414
REACT_CONTEXT_TYPE,
1515
REACT_ELEMENT_TYPE,
16+
REACT_FORWARD_REF_TYPE,
1617
REACT_FRAGMENT_TYPE,
1718
REACT_PORTAL_TYPE,
1819
REACT_PROVIDER_TYPE,
@@ -37,6 +38,7 @@ export function typeOf(object: any) {
3738

3839
switch ($$typeofType) {
3940
case REACT_CONTEXT_TYPE:
41+
case REACT_FORWARD_REF_TYPE:
4042
case REACT_PROVIDER_TYPE:
4143
return $$typeofType;
4244
default:
@@ -55,6 +57,7 @@ export const AsyncMode = REACT_ASYNC_MODE_TYPE;
5557
export const ContextConsumer = REACT_CONTEXT_TYPE;
5658
export const ContextProvider = REACT_PROVIDER_TYPE;
5759
export const Element = REACT_ELEMENT_TYPE;
60+
export const ForwardRef = REACT_FORWARD_REF_TYPE;
5861
export const Fragment = REACT_FRAGMENT_TYPE;
5962
export const Portal = REACT_PORTAL_TYPE;
6063
export const StrictMode = REACT_STRICT_MODE_TYPE;
@@ -75,6 +78,9 @@ export function isElement(object: any) {
7578
object.$$typeof === REACT_ELEMENT_TYPE
7679
);
7780
}
81+
export function isForwardRef(object: any) {
82+
return typeOf(object) === REACT_FORWARD_REF_TYPE;
83+
}
7884
export function isFragment(object: any) {
7985
return typeOf(object) === REACT_FRAGMENT_TYPE;
8086
}

packages/react-is/src/__tests__/ReactIs-test.js

+9
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@ describe('ReactIs', () => {
7676
expect(ReactIs.isElement(<React.StrictMode />)).toBe(true);
7777
});
7878

79+
it('should identify ref forwarding component', () => {
80+
const RefForwardingComponent = React.forwardRef((props, ref) => null);
81+
expect(ReactIs.typeOf(<RefForwardingComponent />)).toBe(ReactIs.ForwardRef);
82+
expect(ReactIs.isForwardRef(<RefForwardingComponent />)).toBe(true);
83+
expect(ReactIs.isForwardRef({type: ReactIs.StrictMode})).toBe(false);
84+
expect(ReactIs.isForwardRef(<React.unstable_AsyncMode />)).toBe(false);
85+
expect(ReactIs.isForwardRef(<div />)).toBe(false);
86+
});
87+
7988
it('should identify fragments', () => {
8089
expect(ReactIs.typeOf(<React.Fragment />)).toBe(ReactIs.Fragment);
8190
expect(ReactIs.isFragment(<React.Fragment />)).toBe(true);

packages/react-reconciler/src/ReactFiber.js

+5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
HostPortal,
2626
CallComponent,
2727
ReturnComponent,
28+
ForwardRef,
2829
Fragment,
2930
Mode,
3031
ContextProvider,
@@ -35,6 +36,7 @@ import getComponentName from 'shared/getComponentName';
3536
import {NoWork} from './ReactFiberExpirationTime';
3637
import {NoContext, AsyncMode, StrictMode} from './ReactTypeOfMode';
3738
import {
39+
REACT_FORWARD_REF_TYPE,
3840
REACT_FRAGMENT_TYPE,
3941
REACT_RETURN_TYPE,
4042
REACT_CALL_TYPE,
@@ -357,6 +359,9 @@ export function createFiberFromElement(
357359
// This is a consumer
358360
fiberTag = ContextConsumer;
359361
break;
362+
case REACT_FORWARD_REF_TYPE:
363+
fiberTag = ForwardRef;
364+
break;
360365
default:
361366
if (typeof type.tag === 'number') {
362367
// Currently assumed to be a continuation and therefore is a

packages/react-reconciler/src/ReactFiberBeginWork.js

+14
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
CallComponent,
2727
CallHandlerPhase,
2828
ReturnComponent,
29+
ForwardRef,
2930
Fragment,
3031
Mode,
3132
ContextProvider,
@@ -153,6 +154,17 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
153154
}
154155
}
155156

157+
function updateForwardRef(current, workInProgress) {
158+
const render = workInProgress.type.render;
159+
const nextChildren = render(
160+
workInProgress.pendingProps,
161+
workInProgress.ref,
162+
);
163+
reconcileChildren(current, workInProgress, nextChildren);
164+
memoizeProps(workInProgress, nextChildren);
165+
return workInProgress.child;
166+
}
167+
156168
function updateFragment(current, workInProgress) {
157169
const nextChildren = workInProgress.pendingProps;
158170
if (hasLegacyContextChanged()) {
@@ -1130,6 +1142,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
11301142
workInProgress,
11311143
renderExpirationTime,
11321144
);
1145+
case ForwardRef:
1146+
return updateForwardRef(current, workInProgress);
11331147
case Fragment:
11341148
return updateFragment(current, workInProgress);
11351149
case Mode:

packages/react-reconciler/src/ReactFiberCompleteWork.js

+3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
ReturnComponent,
3333
ContextProvider,
3434
ContextConsumer,
35+
ForwardRef,
3536
Fragment,
3637
Mode,
3738
} from 'shared/ReactTypeOfWork';
@@ -603,6 +604,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
603604
case ReturnComponent:
604605
// Does nothing.
605606
return null;
607+
case ForwardRef:
608+
return null;
606609
case Fragment:
607610
return null;
608611
case Mode:

packages/react/src/React.js

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
isValidElement,
2525
} from './ReactElement';
2626
import {createContext} from './ReactContext';
27+
import forwardRef from './forwardRef';
2728
import {
2829
createElementWithValidation,
2930
createFactoryWithValidation,
@@ -45,6 +46,7 @@ const React = {
4546
PureComponent,
4647

4748
createContext,
49+
forwardRef,
4850

4951
Fragment: REACT_FRAGMENT_TYPE,
5052
StrictMode: REACT_STRICT_MODE_TYPE,

packages/react/src/ReactElementValidator.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
REACT_ASYNC_MODE_TYPE,
2323
REACT_PROVIDER_TYPE,
2424
REACT_CONTEXT_TYPE,
25+
REACT_FORWARD_REF_TYPE,
2526
} from 'shared/ReactSymbols';
2627
import checkPropTypes from 'prop-types/checkPropTypes';
2728
import warning from 'fbjs/lib/warning';
@@ -297,7 +298,8 @@ export function createElementWithValidation(type, props, children) {
297298
(typeof type === 'object' &&
298299
type !== null &&
299300
(type.$$typeof === REACT_PROVIDER_TYPE ||
300-
type.$$typeof === REACT_CONTEXT_TYPE));
301+
type.$$typeof === REACT_CONTEXT_TYPE ||
302+
type.$$typeof === REACT_FORWARD_REF_TYPE));
301303

302304
// We warn in this case but don't throw. We expect the element creation to
303305
// succeed and there will likely be errors in render.

0 commit comments

Comments
 (0)