Red Baron
Red Baron

Reputation: 7672

How to pass arguments to a function inside compose?

I have a function like so:

export const fireView = (prop1, prop2) => WrappedComponent =>
    class extends Component {
    //stuff in here
} 

then a hoc like this:

export const withFireView = (prop1, prop2) =>
    fireView(prop1, prop2)

but now I want to put this function inside compose as I need to call it with mapStateToProps

so I did this:

compose (
    connect(mapStateToProps),
    fireView
)

but it breaks because You must pass a component to the function returned by connect

so I then I get rid of the arguments in the fireview function and I think this will work but then all the arguments are now undefined inside the function as I've not passed them

Is there any way to do something like this:

compose (
    connect(mapStateToProps),
    fireView(arg1, arg2)
)

but obviously, they are not defined there if that makes sense.

Upvotes: 0

Views: 2537

Answers (3)

Sahil Patel
Sahil Patel

Reputation: 734

I had a similar requirement and ended up on this page. I supposed we were not able to understand the use case. I will try to summarise it and also provide an answer to it.

Explanation

We can use HOC (Higher Order Components) in React to abstract out common functionality. In React docs, we can see the 'with' notation to use these HOCs. I have created a few on my app as well.

withSplashScreenRedirect, withHistoryObject

All these HOCs that I created takes a Component (WrappedComponent) and returns a Component. (No additional Arguments required).

const EnhancedComponent = higherOrderComponent(WrappedComponent);

I would generally have around 2 to 3 HOCs on each Screen level Component. Writing these HOCs as functions returning other functions look ugly. Redux provides a useful compose function. Compose helps us to chain these HOCs in a more readable manner.

From compose docs.

All compose does is let you write deeply nested function transformations without the rightward drift of the code. Don't give it too much credit!

Soon I wanted to create another HOC that would standardise my API call logic and API call handling logic. In this case, I had to send two additional functions to my HOC (along with the Wrapped Component).

withApiCallHandler(WrappedComponent, makeApiCall, handleApiResponse)

Passing props to HOC is not a big deal. React Docs have a good example explaining how to do it. However, adding such an HOC to compose chain is not so straightforward. I guess that is what OP was trying to ask here.

Answer

I wasn't able to find a way to chain such a HOC with compose. Maybe it's not possible. The best solution would be to initialise the withApiCallHandler Component separately and then chain it with compose.

Instead of

const ComposedScreenComponent = compose(
  withHOC1,
  withHOC2,
  withHOC3(arg1, arg2)
)(ScreenComponent);

Do this

const withHOC3AndArgs = (WrappedComponent) =>
  withHOC3(WrappedComponent, arg1, arg2);

const ComposedScreenComponent = compose(
  withHOC1,
  withHOC2,
  withHOC3AndArgs(arg1, arg2)
)(ScreenComponent);

This is not the best answer to the question, but if someone is stuck then this solution will surely be a good workaround.

Upvotes: 1

di3
di3

Reputation: 594

Here is a full working example:

var Component = React.Component;
var render = ReactDOM.render;
var Provider = ReactRedux.Provider;
var connect = ReactRedux.connect;
var createStore = Redux.createStore;
var compose = Redux.compose;
    
const reducer = () => {return {}};
const mapStateToProps = () => {
  return {}; 
};
    
const wrapped = (props) => {
  return <div>{props.prop1} {props.prop2}</div>;
}
const fireView = (prop1, prop2) => WrappedComponent => {
  return class extends Component {
    render() {
      return <WrappedComponent prop1={prop1} prop2={prop2} />; 
    }   
  }
} 
    
const withFireView = (prop1, prop2) => fireView(prop1, prop2);
    
const withFireViewAndConnect = (arg1, arg2) => compose(connect(mapStateToProps), withFireView(arg1, arg2));
    
const App = withFireViewAndConnect('some', 'arg')(wrapped);
    
render(<Provider store={createStore(reducer)}><App /></Provider>, document.getElementById('demo'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.13.0/polyfill.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/redux.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/6.0.0/react-redux.js"></script>

<div id="demo"></div>

Upvotes: 1

Huy Ngo
Huy Ngo

Reputation: 382

You can make withFireView return a function instead. All will work.

export const withFireView = (prop1, prop2) => () => fireView(prop1, prop2)

Upvotes: 0

Related Questions