The function in your question is an HOF (high-order-function) of name withMyApi which returns an anonymous function which accepts a component as WrappedComponent argument which, when called, returns the "Component" you passed.
Here is the syntax to use this HOF:
const MyComponent = withMyApi()(MyOriginalComponent)
Yes, you can say that withMyApi()(MyOriginalComponent) is unnecessary work, it should have just been withMyApi(MyOriginalComponent).
And I agree. So, here are refactored HOCs:
Option 1, using a class component as the wrapper:
export function withMyApiClass(WrappedComponent) {
return class MyApiUrls extends React.Component {
constructor(props) {
super(props)
this.api = 'http://www.example.com'
}
render() {
return <WrappedComponent api={this.api} {...this.props} />
}
}
}
Syntax to use the above HOC: const Comp1 = withMyApiClass(Comp).
Option 2, using a function component as the wrapper:
export function withMyApiFunction(WrappedComponent) {
return function MyApiUrls(props) {
const api = useRef('http://www.example.com')
return <WrappedComponent api={api.current} {...props} />
}
}
Syntax to use the above HOC: const Comp1 = withMyApiFunction(Comp).
So, why do we need the first form (in question) of withMyApi HOF?
You may need or may not, depending on your requirement. A typical use-case would be to pass some config object. An example:
export function withMyApi(config) {
const { secure } = config
return function (WrappedComponent) {
class MyApiUrls extends React.Component {
constructor(props) {
super(props)
this.api = `http${secure ? 's' : ''}://www.example.com`
}
render() {
return <WrappedComponent api={this.api} {...this.props} />
}
}
return MyApiUrls
}
}
Syntax to use the above HOF: const Comp1 = withMyApi({ secure: false })(Comp).
PS: If you ever used Redux, you must have seen connect HOF which accepts a few arguments, similar to your HOF in question.
So the basic idea behind writing HOFs is - separating config parameter from the component parameter to allow people to easily use compose function when there are multiple HOCs to use.
Your questions:
How does the above work?
HOC in question is actually high-order-function: It returns a function which, when called, returns a component.
Is the HOF returning a function component here?
No, It is returning a function. But when we call the returned function, it returns a class component. See the option 2, that returns a function component.
Is the above way intentionally used to be able to compose?
Yes, you can use compose utility (from any library like - Ramdajs, Lodash, or Redux) to create a new "composed HOC" when using multiple HOCs or HOFs. See Maximizing Composability.
Edit:
I don't like HOF. Can I still achieve composition and configuration?
Yes, an example. Let's say we have written below HOC:
export function withMyApi(WrappedComponent, config) {
const { secure } = config
return function MyApiUrls(props) {
const api = useRef(`http${secure ? 's' : ''}://www.example.com`)
return <WrappedComponent api={api.current} {...props} />
}
}
How to use it?
const Comp1 = withMyApi(Comp, { secure: true })
/* OR, if you want to use more HOCs.
Nested (dirty? Maybe) */
const Comp1 = withRouter(withMyApi(Comp, { secure: true }))
-----------------------------------------------------------
// But you CAN NOT compose like this:
const composedHOC = compose(
withRouter, // a function : OK
withMyApi(Comp, { secure: false }) // NOT OK. Because is a React Component now.
)
/* It will give error when you will call composedHOC(...) because
in React, we don't call components using parenthesis i.e. () but
we invoke then using angle brackets i.e. < />. */
/* After compsing, you CAN NOT do the below otherwise you are going
to pass `Comp` 2 times to `withMyApi` : Bad/Confusing... :(
PLUS you will see errors due to calling a React Component using
parenthesis i.e. () as mentioned above. */
const Comp1 = composedHOC(Comp)
-----------------------------------------------------------
// But you can compose like the below:
const composedHOC = compose(
withRouter,
withMyApi
)
const Comp1 = composedHOC(Comp, { secure: false })
/* It will work because it will pass the TWO parameters - "Comp" &
"{ secure: false }" to LAST (withMyApi) HOC. And the result of that
will be passed to FIRST HOC (withRouter). */
-----------------------------------------------------------
// But, surprisingly, there will be error when you change the order:
const composedHOC = compose(
withMyApi, // FIRST
withRouter // LAST
)
const Comp1 = composedHOC(Comp, { secure: false })
/* Because it will pass the TWO parameters to LAST HOC (withRouter),
and result of that to FIRST HOC (withMyApi) and So, withMyApi will
receive ONLY enhanced "Comp" NOT the "{ secure: false }". This is why
using "compose" is buggy for HOCs that acpect config parameters. */
/* Just to remind, this is what a compose function does:
"Composes single-argument functions from right to left. The rightmost
function can take multiple arguments as it provides the signature for
the resulting composite function."
compose(f, g, h) is the same as (...args) => f(g(h(...args)))
Means, it pass args to h, and the result to g and then result to f. */
Ok, I may like HOF. How to do the same using HOF?
export function withMyApi(config) {
const { secure } = config
return function (WrappedComponent) {
return function MyApiUrls(props) {
const api = useRef(`http${secure ? 's' : ''}://www.example.com`)
return <WrappedComponent api={api.current} {...props} />
}
}
}
How to use it?
const Comp1 = withMyApi({ secure: false })(Comp)
// OR, if you want to use more HOCs
const composedHOC = compose(
withRouter,
withMyApi({ secure: false })
)
const Comp1 = composedHOC(Comp) // Looks clean :)
Conclusion:
- Don't use
compose with HOCs if they accept "component" and one or more "config" object. Use nested approach instead.
- Use nested or the better -
compose with HOFs and HOCs (as long as HOCs don't accept a config object)
- It is best to write HOFs when you want to accept one/more config objects. So that, people can use
compose with your HOF.
HOF is better / cleaner if you want to support "configuration" through additional parameters to the function.
This (HOF) form may seem confusing or unnecessary, but it has a useful property. Single-argument HOCs like the one returned by the connect function have the signature Component => Component. Functions whose output type is the same as its input type are really easy to compose together.