Higher Order Component - Part 2

Higher order Component in React Native – Part 2

tl;dr

In my previous post, I explain about write a HoC, encourage more reuse of codebase. This post I will try to explain more example, to make a HoC more flexible (certain use case).

Dynamic Props

import React, { Component } from "react";

export default function withTopStories(WrappedComponent) {
  return class extends Component {
    constructor(props) {
      super();

      this.state = {
        stories: []
      }
    }
    
    componentDidCatch() {
      // error handling
    }

    async componentDidMount() {
      try {
        const res = await fetch('https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty', { method: "GET" }).then(res => res.json());

        if (res) {
          this.setState({ stories: res })
        }
      } catch (error) {
        // error handling
      }
    }

    render() {
      const { stories } = this.state;
      return <WrappedComponent {...this.props} stories={stories} />
    }
  }
}

Given example above from my previous post. We facing an issue as following:

  • It’s only allow developer to fetch topstories resource from HackerNews API. (may not be a bad case, sometimes)
// latest item from HackerNews API
https://hacker-news.firebaseio.com/v0/maxitem.json?print=pretty

// Up to 500 top stories / new stories / best stories
https://hacker-news.firebaseio.com/v0/[topstories|newstories|beststories].json?print=pretty

// Up to 200 Ask / Job / Show stories
https://hacker-news.firebaseio.com/v0/[askstories|showstories|jobstories].json?print=pretty

It’s okay that if you build each API endpoint a standalone HoC (For single responsibility pattern or maybe due to business logic is vary). What if you can reuse the same HoC even for some combination too.

Below is an example, make it as dynamic as possible (Please prioritize maintaining rather than dynamic use case, as in most of the time the most dynamic function is the one hardest to maintain and debug, trade off)

import React, { Component } from "react";

export default function withStories(endpoint, dynamicProps) {
  return (WrappedComponent) => {
    return class extends Component {
      constructor(props) {
        super();

        this.state = {
          stories: []
        }
      }
    
      componentDidCatch() {
        // error handling
      }

      async componentDidMount() {
        try {
          const res = await fetch(endpoint, { method: "GET" }).then(res => res.json());

          if (res) {
            this.setState({ stories: res })
          }
        } catch (error) {
          // error handling
        }
      }

      render() {
        const { stories } = this.state;
        return <WrappedComponent 
                {...this.props} 
                {...{ [dynamicProps]: stories }} />
      }
    }
  }
}

Since, HoC is a JavaScript closures. We can modify it to take in and holding additional parameter – endpoint and dynamicProps.

This way a developer can passing any endpoint (Let’s limited to certain purpose or use case, otherwise it will caused the HoC hard to maintain at the end).

Then, the dynamicProps define how a developer accessing the the props. dynamicProps is defined by developer, for two purpose.

  • The business logic / HoC is encapsulated. Developer more focus on input and output. Less worry for handling business logic.
  • To prevent props collision, when it’s come to combine two or more HoC
export default function withStories(endpoint, dynamicProps) {
  return (WrappedComponent) => {
    return class extends React.Component {
      ...
      render() {
        return <WrappedComponent {...{ [dynamicProps]: value }} />
      }
    }
  }
}

Combines two or more HoC

One of the way, to combine multiple HoC as below:

withHoCOne(withHoCTwo(withHoCThree(App)));

Well, it solve the combine multiple HoC issue. At the same time, it create another issue – readability. Yes, the code is hard to read.

Here, recommend some utility to help solving the issue and maintain the code readability.

compose – from several package

This way we can combine multiple HoC without sacrifice the code readability.

compose(
  withHoCOne,
  withHoCTwo,
  withHoCThree,
)(App)

Beware of

Redundant props or some unused props. In ReactJS, a component is re-render when the props changed. So, if a HoC passing some redundant props and the value had changed it will caused the component to re-render. A bug that is hard to trace.

Conclusion

Wisely use of HoC is promoting the reuse of codebase. Improve maintainability and component unit testability.

Thanks for reading.  😁

Higher Order Component

Higher order Component in React Native – Part 1

tl;dr

Wrapped API in Higher order Component to promote reusable component and static component unit testing

Scenario

Create a component, fetching API/resources in componentDidMount. Most of the tutorial, start this way. This is the easier way for anyone to learn fetching API/resources in ReactJS/React Native. I don’t against this pattern. But, things go complicated in real world business. Fetching multiple API or reuse the same business logic (stateful business logic).

Copy paste might solve your problem and create duplicated code or even worse the codebase will grow very fast. What if you can done the job with a function instead of copy paste ? This way you can separate the business logic and presentation component, making component smaller and modular. It will be a good investment in long terms (reduce tech debt).

Wrapped API/resources fetching or business logic in Higher order Component promote the reusable of presentation component and business logic.

** A plain Higher order Component without too much dependencies allow you to share the same code with ReactJS for web or vice versa.

// Example

export default class App extends React.Components {
  componentDidMount() {
    fetch();
    fetch();
  }

  render() {
    return <View>
      {resources}
      {resources}
    </View>
  }
}

How to utilize Higher order Component (HoC) ?

*From here onward, will calling HackerNews API for demo purpose*

Let’s start with fetching topstories from HackerNews API – return a list of story ID. Then, fetching another API for particular story details.

// list of story ids
https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty

// details of story
https://hacker-news.firebaseio.com/v0/item/121003.json?print=pretty

First, let create the first Higher order Component (HoC), for topstories from HackerNews API.

import React, { Component } from "react";

export default function withTopStories(WrappedComponent) {
  return class extends Component {
    constructor(props) {
      super();

      this.state = {
        stories: []
      }
    }
    
    componentDidCatch() {
      // error handling
    }

    async componentDidMount() {
      try {
        const res = await fetch('https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty', { method: "GET" }).then(res => res.json());

        if (res) {
          this.setState({ stories: res })
        }
      } catch (error) {
        // error handling
      }
    }

    render() {
      const { stories } = this.state;
      return <WrappedComponent {...this.props} stories={stories} />
    }
  }
}

In Higher order Component, component was parameterizedWrappedComponent. In above example, the HoC can now be reuse to any component that need to access the topstories resources.

The withTopStories will fetching the topstories resources from HackerNews API and set to the state – stories. Later on, passing the resources as a props to the parameterized component – WrappedComponent.

Accessing the injected props

Now, we want to access the injected props. Maybe a <FlatList />

import React, { Component } from "react";
import { FlatList, Text } from "react-native";
import withTopStories from "path/to/withTopStories";

class App extends Component {
  renderStories({ item, index }) {
    return <Text>{item}</Text>
  }

  render() {
    const { stories } = this.props;
    return <FlatList 
            data={stories} 
            renderItem={this.renderStories} 
            keyExtractor={(item, index) => `${item}-${index}`} 
           />
  }
}

export default withTopStories(App);

Then, we will render a list of number which is the story’s ID from HackerNews API. In order to get the details, we need the ID to fetch another API.

Yet another HoC for another endpoint

Then, we create another HoC for fetching the details of story by the ID.

import React, { Component } from "react";

export default function withStoryDetails(WrappedComponent) {
  return class extends Component {
    constructor(props) {
      super();

      this.state = {
        details: {}
      }
    }

    async componentDidMount() {
      try {
        const { storyID } = this.props;
        const details = await fetch(`https://hacker-news.firebaseio.com/v0/item/${storyID}.json?print=pretty`, { method: "GET" }).then(res => res.json());

        if (details) {
          this.setState({ details })
        }
      } catch (error) {
        // error handling
      }
    }

    render() {
      const { storyID, ...rest } = this.props;
      const { details } = this.state;
      return <WrappedComponent {...rest} details={details} />
    }
  }
}

Now, we need a component to display the details. Let’s create one. This time we won’t wrapped it directly, instead we will create a static version. This allow us easy to mock the data and unit testing the component.

import React from "react";
import { Text } from "react-native"

export default function Story(props) {
  const { details, ...rest } = props;
  return <Text {...rest}>{details.title}</Text>
}
/**
 * Version 2.0
 */
import React, { Component } from "react";
import { FlatList } from "react-native";
import withTopStories from "path/to/withTopStories";
import withStoryDetails from "path/to/withStoryDetails";
import Story from "path/to/Story"; // the component we create above

const WrappedStory = withStoryDetails(props => {
  const { storyID, ...rest } = props;
  return <Story storyID={storyID} {...rest} />
});

class App extends Component {
  renderStories({ item, index }) {
    // replace the below component 
    return <WrappedStory storyID={item} />
  }

  render() {
    const { stories } = this.props;
    return <FlatList 
             data={stories} 
             renderItem={this.renderStories} 
             keyExtractor={(item, index) => `${item}-${index}`} 
           />
  }
}

export default withTopStories(App);

Closing

Now, both React Native and ReactJS can share the HoC. Use it on mobile or web. Wrapped it with any presentation component without duplicate code. Easier to unit testing static component.

React Native 0.60

What’s new in React Native 0.60 ?

tl;dr


What’s new introduce in React Native 0.60 ?

  • Improvement on accessibility for both Android and iOS
  • Nested <View />within <Text /> components, but with limitation
  • touchSoundDisabled props introduce to <Button />, <Touchable />and <TouchableWithoutFeedback />for android only
  • Included a default eslintrc.js config file
  • Migration to AndroidX (Enabled by default)
  • Auto linking (Linking native modules)

  • New Introduction App Screen

Starting React Native 0.60 above modules will totally remove from React Native Core. In order to utilize specific module you need to install it as separate library from react-native-community.


Why you shouldn’t to React Native 0.60 immediately ?

Bug fixes and new features that introduce in React Native 0.60 is great. But, you may want to take consideration before upgrading.

The main issue is that React Native 0.60 migrate to AndroidX where most of the library has not ready yet for this changes. So, in order to upgrade to React Native 0.60. You may need to ensure all the third-party plugins/library in your application is compatible with AndroidX. Although, soon they will migrate to support AndroidX too. But, not at the moment.


Upgrade Helper

Upgrade helper – upgrade made easy. A new web tools that can help developer upgrading their react native easily. As it is comparing the different of two different react native version and show the difference side by side in web browser.