2.1.0 • Published 3 years ago

react-render-ctrl v2.1.0

Weekly downloads
3
License
MIT
Repository
-
Last release
3 years ago

React-Render-Ctrl

npm version

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

Demo

Versions

v1.x

initial version

v2.x

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

Table of Content

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
propstypedefaultdescription
ErrorComponentelementnull
EmptyComponentelementnull
LoadingComponentelementnull
EnhancedComponent

EnhancedComponent is the return of withRenderCtrl.

propstypedefaultdescription
isErrorboolfalse
isLoadingboolfalse
isDataReadyboolfalse
errorComponentPropsObject{}props for customized error component to show specific information
loadingComponentPropsObject{}props for customized loading component to show specific information
emptyComponentPropsObject{}props for customized empty component to show specific information
shouldReloadEverytimeboolfalsealways show <LoadingComponent /> while isLoading is true even if data is ready
debugboolfalselog debug info in the console while process.env.NODE_ENV !== 'production'

License

MIT

2.1.0

3 years ago

2.0.0

3 years ago

1.1.1

6 years ago

1.1.0

6 years ago

1.0.0-beta.2

6 years ago

1.0.0-beta.1

6 years ago

1.0.0-beta.0

6 years ago

1.0.0-dev5

6 years ago

1.0.0-dev4

6 years ago

1.0.0-dev3

6 years ago

1.0.0-dev2

6 years ago

1.0.0-dev1

6 years ago

1.0.0-beta1

6 years ago

1.0.0

6 years ago