Support ref-forwarding
Hello together! Since the newest React-Version forwarding of refs is supported. Should this be used in this library? This would make the recompose HOCs truly transparent. This would be a breaking change of the API, because some users might be using the lifecycle HOC to add public methods to their instances. This would no longer work, when all recompose HOCs forward refs.
Just use React.forwardRef when it is needed. Wrap any enhanced component whenever needed as like as in React doc https://reactjs.org/docs/forwarding-refs.html What the reason to provide ref forwarding for every enhancer? refs are rarely used thing. There are other enhancers libraries , custom enhancers - all that break composability - some enhancers forwardRefs, some are not. So React.forwardRef api looks the best.
Hey, everyone! So what is exactly the best way to make forwardRef() work with recompose'd components? F.ex :
const Comp = React.forwardRef(({focused, ...rest}, ref) => <input className={classnames(styles.comp, focused && styles.focused)} ref={ref} />);
const EnhancedComp = compose(
withState('focused', 'setFocused', false),
mapProps(({ setFocused, ...rest }) => ({
onBlur: () => setFocused(false),
onFocus: () => setFocused(true),
...rest
}))
)(Comp);
// Won't work like this
<EnhancedComp {...props} ref={someRef} />
// ...because ref is not a prop and won't be passed along with props through wrappers.
// This also won't work
React.forwardRef((props, ref) => <EnhancedComp {...props} ref={ref}/>)
// ...because ref captures top-most wrapper instead of <input>
Indeed, reading documentation helps! I've found out a way to do this.
const Comp = ({focused, forwardedRef, ...rest}) => <input className={classnames(styles.comp, focused && styles.focused)} ref={forwardedRef} />);
const EnhancedComp = compose(
withState('focused', 'setFocused', false),
mapProps(({ setFocused, ...rest }) => ({
onBlur: () => setFocused(false),
onFocus: () => setFocused(true),
...rest
}))
)(Comp);
// This works! Because forwardedRef is now treated like a regular prop.
const EnhancedWithRef = React.forwardRef(({...props}, ref)) => <EnhancedComp {...props} forwardedRef={ref} />);
Hi. Just faced same issue, that is not possible to workaround. I think issue is in branch HOC.
When using together with another HOC, that adds ref, I'm not sure how I can workaround it, except adding toClass above branch.
For example, DragLayer from react-dnd adds ref to its wrapped component: https://github.com/react-dnd/react-dnd/blob/master/packages/react-dnd/src/DragLayer.tsx#L105
This gives refs warning:
compose(
DragLayer,
branch,
);
This does not gives refs warning:
compose(
DragLayer,
toClass,
branch,
);
This is relatively easy to add support in your own code base, but I think it'd be nice to have as a helper here as well. Here's what I'm doing right now:
const enhance: HOC<*, EnhancedProps> = compose(
forwardRefs(),
mapProps(/* ... */),
restoreRefs(),
);
/* @flow */
/* eslint-disable react/no-multi-comp */
import React, {
type ComponentType,
} from 'react';
import {
getDisplayName,
compose as baseCompose,
type HOC,
} from 'recompose';
import hoistStatics from 'hoist-non-react-statics';
export const compose: $Compose = ((...funcs: *) => (
baseCompose(
forwardRefs(),
...funcs,
restoreRefs(),
)
): any);
export const forwardRefs = <Base, Enhanced>({
propName = 'forwardedRef',
}: {
propName: string,
} = {}): HOC<Base, Enhanced> => ((
Component: ComponentType<Base>,
): ComponentType<Enhanced> => {
const forwarder = (props: *, ref: *) => (
<Component { ...{ [propName]: ref, ...props } } />
);
const Container = (React: any).forwardRef(forwarder);
const displayName = getDisplayName(Component);
forwarder.displayName = displayName;
Container.displayName = displayName;
hoistStatics(Container, Component);
return Container;
});
export const restoreRefs = <Base, Enhanced>({
propName = 'forwardedRef',
}: {
propName: string,
} = {}): HOC<Base, Enhanced> => ((
Component: ComponentType<Base>,
): ComponentType<Enhanced> => {
const Container = ({ [propName]: ref, ...props }: *) => (
<Component ref={ ref } { ...props } />
);
Container.displayName = getDisplayName(Component);
return (Container: any);
});
After much searching, I ended up creating a withForwardingRef
const withForwardingRef = <Props extends {[_: string]: any}>(BaseComponent: React.ReactType<Props>) =>
React.forwardRef((props, ref) => <BaseComponent {...props} forwardedRef={ref} />);
export default withForwardingRef;
usage:
const Comp = ({forwardedRef}) => (
<button ref={forwardedRef} />
)
const MyBeautifulComponent = withForwardingRef<Props>(Comp); // Now Comp has a prop called forwardedRef
usage of usage:
<MyBeautifulComponent ref={someRef} />