Skip to content

Commit

Permalink
RFC facebook#30: React.forwardRef implementation (facebook#12346)
Browse files Browse the repository at this point in the history
Added React.forwardRef support to react-reconciler based renders and the SSR partial renderer.
  • Loading branch information
bvaughn authored and LeonYuAng3NT committed Mar 22, 2018
1 parent ba1d11f commit b55a648
Show file tree
Hide file tree
Showing 13 changed files with 368 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,25 @@ describe('ReactDOMServerIntegration', () => {
expect(component.refs.myDiv).toBe(root.firstChild);
});
});

it('should forward refs', async () => {
const divRef = React.createRef();

class InnerComponent extends React.Component {
render() {
return <div ref={this.props.forwardedRef}>{this.props.value}</div>;
}
}

const OuterComponent = React.forwardRef((props, ref) => (
<InnerComponent {...props} forwardedRef={ref} />
));

await clientRenderOnServerString(
<OuterComponent ref={divRef} value="hello" />,
);

expect(divRef.current).not.toBe(null);
expect(divRef.current.textContent).toBe('hello');
});
});
20 changes: 20 additions & 0 deletions packages/react-dom/src/server/ReactPartialRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import describeComponentFrame from 'shared/describeComponentFrame';
import {ReactDebugCurrentFrame} from 'shared/ReactGlobalSharedState';
import {warnAboutDeprecatedLifecycles} from 'shared/ReactFeatureFlags';
import {
REACT_FORWARD_REF_TYPE,
REACT_FRAGMENT_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_ASYNC_MODE_TYPE,
Expand Down Expand Up @@ -841,6 +842,25 @@ class ReactDOMServerRenderer {
}
if (typeof elementType === 'object' && elementType !== null) {
switch (elementType.$$typeof) {
case REACT_FORWARD_REF_TYPE: {
const element: ReactElement = ((nextChild: any): ReactElement);
const nextChildren = toArray(
elementType.render(element.props, element.ref),
);
const frame: Frame = {
type: null,
domNamespace: parentNamespace,
children: nextChildren,
childIndex: 0,
context: context,
footer: '',
};
if (__DEV__) {
((frame: any): FrameDev).debugElementStack = [];
}
this.stack.push(frame);
return '';
}
case REACT_PROVIDER_TYPE: {
const provider: ReactProvider<any> = (nextChild: any);
const nextProps = provider.props;
Expand Down
6 changes: 6 additions & 0 deletions packages/react-is/src/ReactIs.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
REACT_ASYNC_MODE_TYPE,
REACT_CONTEXT_TYPE,
REACT_ELEMENT_TYPE,
REACT_FORWARD_REF_TYPE,
REACT_FRAGMENT_TYPE,
REACT_PORTAL_TYPE,
REACT_PROVIDER_TYPE,
Expand All @@ -37,6 +38,7 @@ export function typeOf(object: any) {

switch ($$typeofType) {
case REACT_CONTEXT_TYPE:
case REACT_FORWARD_REF_TYPE:
case REACT_PROVIDER_TYPE:
return $$typeofType;
default:
Expand All @@ -55,6 +57,7 @@ export const AsyncMode = REACT_ASYNC_MODE_TYPE;
export const ContextConsumer = REACT_CONTEXT_TYPE;
export const ContextProvider = REACT_PROVIDER_TYPE;
export const Element = REACT_ELEMENT_TYPE;
export const ForwardRef = REACT_FORWARD_REF_TYPE;
export const Fragment = REACT_FRAGMENT_TYPE;
export const Portal = REACT_PORTAL_TYPE;
export const StrictMode = REACT_STRICT_MODE_TYPE;
Expand All @@ -75,6 +78,9 @@ export function isElement(object: any) {
object.$$typeof === REACT_ELEMENT_TYPE
);
}
export function isForwardRef(object: any) {
return typeOf(object) === REACT_FORWARD_REF_TYPE;
}
export function isFragment(object: any) {
return typeOf(object) === REACT_FRAGMENT_TYPE;
}
Expand Down
9 changes: 9 additions & 0 deletions packages/react-is/src/__tests__/ReactIs-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ describe('ReactIs', () => {
expect(ReactIs.isElement(<React.StrictMode />)).toBe(true);
});

it('should identify ref forwarding component', () => {
const RefForwardingComponent = React.forwardRef((props, ref) => null);
expect(ReactIs.typeOf(<RefForwardingComponent />)).toBe(ReactIs.ForwardRef);
expect(ReactIs.isForwardRef(<RefForwardingComponent />)).toBe(true);
expect(ReactIs.isForwardRef({type: ReactIs.StrictMode})).toBe(false);
expect(ReactIs.isForwardRef(<React.unstable_AsyncMode />)).toBe(false);
expect(ReactIs.isForwardRef(<div />)).toBe(false);
});

it('should identify fragments', () => {
expect(ReactIs.typeOf(<React.Fragment />)).toBe(ReactIs.Fragment);
expect(ReactIs.isFragment(<React.Fragment />)).toBe(true);
Expand Down
5 changes: 5 additions & 0 deletions packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
HostPortal,
CallComponent,
ReturnComponent,
ForwardRef,
Fragment,
Mode,
ContextProvider,
Expand All @@ -35,6 +36,7 @@ import getComponentName from 'shared/getComponentName';
import {NoWork} from './ReactFiberExpirationTime';
import {NoContext, AsyncMode, StrictMode} from './ReactTypeOfMode';
import {
REACT_FORWARD_REF_TYPE,
REACT_FRAGMENT_TYPE,
REACT_RETURN_TYPE,
REACT_CALL_TYPE,
Expand Down Expand Up @@ -357,6 +359,9 @@ export function createFiberFromElement(
// This is a consumer
fiberTag = ContextConsumer;
break;
case REACT_FORWARD_REF_TYPE:
fiberTag = ForwardRef;
break;
default:
if (typeof type.tag === 'number') {
// Currently assumed to be a continuation and therefore is a
Expand Down
14 changes: 14 additions & 0 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
CallComponent,
CallHandlerPhase,
ReturnComponent,
ForwardRef,
Fragment,
Mode,
ContextProvider,
Expand Down Expand Up @@ -153,6 +154,17 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
}
}

function updateForwardRef(current, workInProgress) {
const render = workInProgress.type.render;
const nextChildren = render(
workInProgress.pendingProps,
workInProgress.ref,
);
reconcileChildren(current, workInProgress, nextChildren);
memoizeProps(workInProgress, nextChildren);
return workInProgress.child;
}

function updateFragment(current, workInProgress) {
const nextChildren = workInProgress.pendingProps;
if (hasLegacyContextChanged()) {
Expand Down Expand Up @@ -1130,6 +1142,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
workInProgress,
renderExpirationTime,
);
case ForwardRef:
return updateForwardRef(current, workInProgress);
case Fragment:
return updateFragment(current, workInProgress);
case Mode:
Expand Down
3 changes: 3 additions & 0 deletions packages/react-reconciler/src/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
ReturnComponent,
ContextProvider,
ContextConsumer,
ForwardRef,
Fragment,
Mode,
} from 'shared/ReactTypeOfWork';
Expand Down Expand Up @@ -603,6 +604,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
case ReturnComponent:
// Does nothing.
return null;
case ForwardRef:
return null;
case Fragment:
return null;
case Mode:
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/React.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
isValidElement,
} from './ReactElement';
import {createContext} from './ReactContext';
import forwardRef from './forwardRef';
import {
createElementWithValidation,
createFactoryWithValidation,
Expand All @@ -45,6 +46,7 @@ const React = {
PureComponent,

createContext,
forwardRef,

Fragment: REACT_FRAGMENT_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
Expand Down
4 changes: 3 additions & 1 deletion packages/react/src/ReactElementValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
REACT_ASYNC_MODE_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONTEXT_TYPE,
REACT_FORWARD_REF_TYPE,
} from 'shared/ReactSymbols';
import checkPropTypes from 'prop-types/checkPropTypes';
import warning from 'fbjs/lib/warning';
Expand Down Expand Up @@ -297,7 +298,8 @@ export function createElementWithValidation(type, props, children) {
(typeof type === 'object' &&
type !== null &&
(type.$$typeof === REACT_PROVIDER_TYPE ||
type.$$typeof === REACT_CONTEXT_TYPE));
type.$$typeof === REACT_CONTEXT_TYPE ||
type.$$typeof === REACT_FORWARD_REF_TYPE));

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

0 comments on commit b55a648

Please sign in to comment.