HOC pass through properties

Viewed 901

I'm lately struggling with complex HOC and how I can pass through only the new props defined in it and not any other. More precisely, suppose my HOC makes use of other HOCs which extends its properties, for instance

const withSession = (WrappedComponent) => {

  class SessionProvider extends React.PureComponent {
    constructor(props) {
      super(props);
      this.login = this.login.bind(this);
    }

    login() {
      console.log('login'); // this will dispatch some action
      // this.props.dispatch... 
    }

    render() {

      return (
        <WrappedComponent
          doLogin={this.login}
          {...this.props} 
        />
      );
    }
  }

  const mapStateToProps = null;

  function mapDispatchToProps(dispatch) {
    return {
      dispatch,
    };
  }

 const withConnect = connect(mapStateToProps, mapDispatchToProps);

 return compose(
    withConnect,
    withRouter,
  )(injectIntl(SessionProvider));
};

Here the SessionProvider makes use of dispatch and injectIntl which attach properties to its props. However, I don't want to pass those props down to the wrapped component. The idea is to have a SessionProvider HOC which has some API call but only extends the wrapped component with login. I noticed that if keep {...this.props}, the wrapped component will also get all the props used by the HOC which I don't want to pass through. So I thought to explicitly define which properties to pass through by decomposing this.props by changing the HOC render method:

render() {
  const { dispatch, intl, ...otherProps } = this.props;
  return <WrappedComponent doLogin={this.login} { ...otherProps} />;
}

However what happens with this is that if the WrappedComponent itself has dispach or intl props, those are not passed-through the HOC.

Is there anything wrong in what I'm doing? Any better approach? Am I missing anything?

2 Answers

There's nothing wrong in what you're doing. Prop name conflicts is a known issue when using HOCs. So, as far as I can tell, the best alternative you could use is Render Props pattern, which helps to keep components render as declarative as possible. For your case, consider something like this:

class Session extends React.PureComponent {
  constructor(props) {
    super(props);
    this.login = this.login.bind(this);
  }

  login() {
    console.log("login"); // this will dispatch some action
    // this.props.dispatch...
  }

  // ...

  render() {
    return (
      <React.Fragment>
        {this.props.children({
          doLogin: this.login
          doLogout: this.logout
          // ...
        })}
      </React.Fragment>
    );
  }
}

// ...

return compose(
  withConnect,
  withRouter
)(injectIntl(Session));

And use it from another components:

// ...
render() {
  return (

    <Session>
      {({ doLogin, doLogout }) => (
        <React.Fragment>
          <SomeComponent doLogin={doLogin} />
          <button onClick={doLogout}>Logout</button>
        </React.Fragment>
      )}
    </Session>

  )
}

UPDATE:

There's a pretty promising Hooks Proposal available in v16.7.0-alpha. I'm not quite familiar with them yet, but they tend to solve components reusability more efficiently.

You need to copy static properties, for that i use below code.. you can add more properties as per your need

export const REACT_STATICS = {
  childContextTypes: true,
  contextTypes: true,
  defaultProps: true,
  displayName: true,
  getDefaultProps: true,
  mixins: true,
  propTypes: true,
  type: true
};

export const KNOWN_STATICS = {
  name: true,
  length: true,
  prototype: true,
  caller: true,
  arguments: true,
  arity: true
};

export function hoistStatics(targetComponent, sourceComponent) {
  var keys = Object.getOwnPropertyNames(sourceComponent);
  for (var i = 0; i < keys.length; ++i) {
    const key = keys[i];
    if (!REACT_STATICS[key] && !KNOWN_STATICS[key]) {
      try {
        targetComponent[key] = sourceComponent[key];
      } catch (error) {}
    }
  }
  return targetComponent;
}


// in HOC
const hoistedSessionProvider = hoistStatics(SessionProvider, WrappedComponent);

// use hoistedSessionProvider in compose
Related