react-render-ctrl

[![npm version](https://img.shields.io/badge/npm-v1.1.1-brightgreen.svg?style=flat-square)](https://www.npmjs.com/package/react-render-ctrl)

Downloads in past

Stats

StarsIssuesVersionUpdatedCreatedSize
react-render-ctrl
2.1.03 years ago6 years agoMinified + gzip package size for react-render-ctrl in KB

Readme

React-Render-Ctrl

npm version
A component render control HOC for different states with zero dependencies.

Versions

v1.x

initial version

v2.x

  • update legacy Context API implement to the new Context API
  • add typescript typing file

Table of Content

- Versions
- [v1.x](#v1x)
- [v2.x](#v2x)
- Table of Content - Intention - Installation - Examples
- [Basic Usage](#basic-usage)
- [With Redux](#with-redux)
- [Default State Component](#default-state-component)
- [Customized State Component](#customized-state-component)
- Render Flow - API
- [withRenderCtrl (WrappedComponent, [StateComponents])](#withrenderctrl-wrappedcomponent-statecomponents)
- [RenderCtrlProvider](#renderctrlprovider)
- [EnhancedComponent](#enhancedcomponent)
- License

Intention

In react development we often face a problem of dealing with different states for some data-driven components. In most cases, those states include:
  • Ideal State. The happy path for the component, everything is fine.
  • Loading State. Component shows something to indicate it is loading.
  • Error State. Component shows something went wrong.
  • Empty State. Component shows something to indicate it is empty.

For those components, you would like to show a proper hint to users base on state of the component. The code may look something like the following: container.js
import MyComponent from 'path/to/my/component';
import ErrorHint from 'path/to/error/hint';
import LoadingSpinner from 'path/to/loading/apinner';
import EmptyHint from 'path/to/empty/hint';

class Container extends React.Component {
  render() {
    return (
      // ...
      {
        isComponentError
        ? <ErrorHint />
        : isLoading
          ? <LoadingSpinner />
          : data.length > 0
            ? <MyComponent data={ data } />
            : <EmptyHint />
      }
      // ...
    )
  }
}
The code above is not ideal, because
  1. Nested Ternary operator. If there are several components all implement this kind of logic, it is not easy to understand at a galance.
  2. Spreading logics. This kind of similar logic can be generalized and be handled in a single place instead of spreading all over the code base.
  3. Verbose importing. If <ErrorHint />, <LoadingSpinner />, <EmptyHint /> are the same across the whole project, you still have to import all of them to wherever they are used. It makes the code more verbose.
  4. Lower cohesion. If <ErrorHint />, <LoadingSpinner />, <EmptyHint /> are specific for the component, then they should be located in the component.js instead of in the container.js for higher cohesion.

To address these problems, I think Provider Pattern would be a good solution. Provider provides global Loading, Empty, Error Components and uses Higher-Order-Component to wrap the component you would like to implement render logic. Like the following, index.js
<RenderCtrlProvider
  ErrorComponent={ () => <div>default error hint</div> }
  EmptyComponent={ () => <div>default empty hint</div> }
  LoadingComponent={ () => <div>default loading hint</div> }
>
  <YourApp />
</RenderCtrlProvider>
YourComponent.js
class YourComponent extends Component {
  //...
}

export default withRenderCtrl(YourComponent, {
  // your customized loading for this component
  LoadingComponent: () => <div>I am loading</div>
});
container.js
class Container extends Component {
  // ...
  render() {
    return (
      // ...
      <YourComponent
        isError={ isComponentError }
        isLoading={ isLoading }
        isDataReady={ data.length > 0 } // or other logics indicate data is ready
        // other props of "YourComponent"...
      />
      // ...
    );
  }
}
This appoarch alleviates the problems we mention above.

Installation

npm install react-render-ctrl or yarn add react-render-ctrl

Examples

The State Components in the following mean
  • LoadingComponent
  • ErrorComponent
  • EmptyComponent

Basic Usage

You can use the Higher-Order-Component withRenderCtrl directly without using RenderCtrlProvider, if you don't need to config your default state components. YourComponent.js
import React from 'react';
import { withRenderCtrl } from 'react-render-ctrl';
// ...
class YourComponent extends React.Component {
  // ...
}
export default withRenderCtrl(YourComponent, {
  ErrorComponent: () => <div>something went wrong</div>,
  EmptyComponent: () => <div>it is very empty</div>,
  LoadingComponent: () => <div>I am loading</div>
});
container.js
class Container extends React.Component {
  // ...
  render() {
    return (
      // ...
      <YourComponent
        isError={ something.went.wrong }
        isLoading={ api.isFetching }
        isDataReady={ data.length > 0 && data[0].value }
      />
      // ...
    );
  }
}

With Redux

Since you are not directly pass props to container which is connected with redux, you can set your isDataReady props in the mapStateToProps function.
import React from 'react';
import { withRenderCtrl } from 'react-render-ctrl';
// ...
class YourComponent extends React.Component {
  // ...
}
function mapStateToProps(state) {
  return  {
    //...
    isDateReady: state.data.length > 0 && data[0].value
  }
}
export default connect(mapStateToProps)(withRenderCtrl(YourComponent, {
  ErrorComponent: () => <div>something went wrong</div>,
  EmptyComponent: () => <div>it is very empty</div>,
  LoadingComponent: () => <div>I am loading</div>
}));
container.js
class Container extends React.Component {
  // ...
  render() {
    return (
      // ...
      <YourComponent
        isError={ something.went.wrong }
        isLoading={ api.isFetching }
        isDataReady={ data.length > 0 && data[0].value }
      />
      // ...
    );
  }
}

Default State Component

If you need to config your default state components, you have to implement <RenderCtrlProvider /> in the root of your application. index.js
ReactDOM.render(
  <RenderCtrlProvider
    ErrorComponent={ () => <div>default error hint</div> }
    EmptyComponent={ () => <div>default empty hint</div> }
    LoadingComponent={ () => <div>default loading hint</div> }
  >
    <YourApp />
  </RenderCtrlProvider>
  ,
  document.getElementById('root')
);
In your component you don't need to pass state components as argument to the withRenderCtrl function. YourComponent.js
import React from 'react';
import { withRenderCtrl } from 'react-render-ctrl';
// ...
class YourComponent extends React.Component {
  // ...
}
export default withRenderCtrl(YourComponent);
container.js
class Container extends React.Component {
  // ...
  render() {
    return (
      // ...
      <YourComponent
        isError={ something.went.wrong }
        isLoading={ api.isFetching }
        isDataReady={ data.length > 0 && data[0].value }
      />
      // ...
    );
  }
}

Customized State Component

As above, you still can provide customized state components to YourComponent. It will overwrite the default state components.
YourComponent.js
import React from 'react';
import { withRenderCtrl } from 'react-render-ctrl';
// ...
class YourComponent extends React.Component {
  // ...
}
export default withRenderCtrl(YourComponent, {
  ErrorComponent: () => <div>customized error component</div>,
  EmptyComponent: () => <div>customized empty component</div>,
  LoadingComponent: () => <div>customized loading component</div>,
});
container.js
class Container extends React.Component {
  // ...
  render() {
    return (
      // ...
      <YourComponent
        isError={ something.went.wrong }
        isLoading={ api.isFetching }
        isDataReady={ data.length > 0 && data[0].value }
      />
      // ...
    );
  }
}

You can also pass specific props to you customized Component, like:
class Container extends React.Component {
  // ...
  render() {
    return (
      // ...
      <YourComponent
        isError={ something.went.wrong }
        isLoading={ api.isFetching }
        isDataReady={ data.length > 0 && data[0].value }
        errorComponentProps={ { errorMsg: 'something went wrong' } }
      />
      // ...
    );
  }
}
then your errorComponent knows what to show base on the errorComponentProps. It works like:
<YourCustomizeErrorComponent
  { ...errorComponentProps }
/>
So do loading and empty Components

Render Flow

Squares with gray background are state components
Render Flow

API

withRenderCtrl (WrappedComponent, StateComponents)
// Arguments Type
WrappedComponent: ReactComponent,
StateComponent: {
  ErrorComponent: ReactComponent,
  EmptyComponent: ReactComponent,
  LoadingComponent: ReactComponent
}
RenderCtrlProvider
|props|type|default|description| |-|-|-|-| |ErrorComponent|element|null|| |EmptyComponent|element|null|| |LoadingComponent|element|null||
EnhancedComponent
EnhancedComponent is the return of withRenderCtrl.
|props|type|default|description| |-|-|-|-| |isError|bool|false|| |isLoading|bool|false|| |isDataReady|bool|false|| |errorComponentProps|Object|{}|props for customized error component to show specific information| |loadingComponentProps|Object|{}|props for customized loading component to show specific information| |emptyComponentProps|Object|{}|props for customized empty component to show specific information| |shouldReloadEverytime|bool|false|always show <LoadingComponent /> while isLoading is true even if data is ready| |debug|bool|false|log debug info in the console while process.env.NODE_ENV !== 'production'|

License

MIT