Skip to content
This repository has been archived by the owner on Sep 10, 2022. It is now read-only.

Support ref-forwarding #640

Open
andrewgreenh opened this issue Apr 5, 2018 · 6 comments
Open

Support ref-forwarding #640

andrewgreenh opened this issue Apr 5, 2018 · 6 comments

Comments

@andrewgreenh
Copy link

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.

@istarkov
Copy link
Contributor

istarkov commented Apr 5, 2018

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.

@Rom325
Copy link

Rom325 commented May 16, 2018

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>

@Rom325
Copy link

Rom325 commented May 18, 2018

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} />);

@ipanasenko
Copy link

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,
);

@wbyoung
Copy link

wbyoung commented Aug 13, 2018

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);
});

@oreporan
Copy link

oreporan commented Nov 22, 2018

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} />

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants