React compose function and higher order function usage

Viewed 298

I have a JSX file MyComponent.jsx defined as below;

import compose from 'compose-function'

const MyComponent = () => {
    //some hooks like useState, useEffect here
    // finally returns some JSX
    return (
        <>
        </>
    )
}

function MyComponentData(props) {
    let StyledMyComponent = compose(
        withStyle(style),
        withTranslation('translation'),
        withMyApi()
    )(MyComponent);
    return (
        <MyContextProvider>
            <StyledMyComponent />
        </MyContextProvider>
    );
}

export default MyComponentData;

Now "withMyApi" looks like below (defined in separate Api.jsx file);

export function withMyApi() {
    // Extra function wrapper. Is this required to compose multiple HOCs ?
    return function(WrappedComponent) {
        class MyApiURLs extends React.Component {
            constructor(props) {
              super(props);
              this.api = {
                dataSources: dataSources
              }
            }
            render() {
              const { title, content } = this.props;
              return (
                <div>
                  <WrappedComponent api={this.api} {...this.props} />
                </div>
              );
            }
        }
        return MyApiURLs;
    }
}

Now here are my questions;

  1. I am trying to understand the usage of "compose-function" dependency. Is it to compose things like styles, class to make API calls, translations, etc
  2. Inside withMyApi, I see an extra function wrapper. Is that required to compose multiple HOCs ? I mean the whole syntax is quite confusing. While I am aware about HOCs, just wanted to understand the syntax usage it his case and specifically in context of how it is consumed in MyComponent.jsx.
  3. What does the "WrappedComponent" refer to exactly in this case ? Is that what is in the next round brackets where I compose i.e. MyComponent in this case ?
1 Answers

I am trying to understand the usage of "compose-function" dependency. Is it to compose things like styles, class to make API calls, translations, etc

Compose is a more readable way to nest functions. This a standard functional programming practice, and strictly related to react.

This:

let StyledMyComponent = compose(
  withStyle(style),
  withTranslation('translation'),
  withMyApi()
)(MyComponent);

is equivalent of this:

let StyledMyComponent = withStyle(style)(withTranslation('translation')(withMyApi()(MyComponent)));

Inside withMyApi, I see an extra function wrapper. Is that required to compose multiple HOCs ? I mean the whole syntax is quite confusing. While I am aware about HOCs, just wanted to understand the syntax usage it his case and specifically in context of how it is consumed in MyComponent.jsx.

The outer function wrapper is used to configure the HoC when wrapping a component. The configuration is static, and doesn't come from props via the standard React rendering process. This is also a standard functional programming practice called currying. You can invoke the function by passing the 1st set of parameters, and then you get another function, that can also accept parameters, and so on. The function will return it's results only when the last returned function is called.

You can see two examples in your code:

  • withStyle(style) - pass the style to use when rendering the component
  • withTranslation('translation') - pass the translation to use when rendering the component

In the case of withMyApi(), you can skip the outer wrapper, because it's not used for anything.

3.

What does the "WrappedComponent" refer to exactly in this case ? Is that what is in the next round brackets where I compose i.e. MyComponent in this case?

As I've shown in the compose example

let StyledMyComponent = withStyle(style)(withTranslation('translation')(withMyApi()(MyComponent)));

after calling withMyApi() a function is returned, which is called with MyComponent, and this is the WrappedComponent.

You can find more information about currying and compose in this article.


There is a problem with your code. When you use MyComponentData, you recreate StyledMyComponent on each render:

function MyComponentData(props) {
  let StyledMyComponent = compose(
    withStyle(style),
    withTranslation('translation'),
    withMyApi()
  )(MyComponent);
  return (
    <MyContextProvider>
      <StyledMyComponent />
    </MyContextProvider>
  );
}

Extract the wrapped component out of MyComponentData, since you only need to create it once:

const StyledMyComponent = compose(
  withStyle(style),
  withTranslation('translation'),
  withMyApi()
)(MyComponent);

function MyComponentData(props) {
  return (
    <MyContextProvider>
      <StyledMyComponent />
    </MyContextProvider>
  );
}
Related