Monday

Exception handling in react with typescript

withRenderCtrl.tsx
import React from 'react';
import Loading from '../../commons/loading/loading';
import ErrorBoundary from '../../commons/error/errorBoundary';
import EmptyHint from '../../commons/empty/emptyHint';

export interface WithRenderCtrlProp {
  isDataReady: boolean;
  isLoading: boolean;
  customEmptyMessage?: string | null;
  errorMessage: string | null;
}

const withRenderCtrl = <P extends object>(Component: React.ComponentType<P>) =>
  class WithLoading extends React.Component<P & WithRenderCtrlProp> {
    render() {
      const { isDataReady, isLoading, errorMessage, customEmptyMessage, ...props } = this.props;

      if (isDataReady) return <Component {...props as P} />;
      if (errorMessage) return <ErrorBoundary errorMessage={errorMessage}/>;
      if (isLoading) return <Loading />;
      return <EmptyHint customMessage={customEmptyMessage}/>;
    }
  }

export default withRenderCtrl;
emptyHint.tsx
import React from 'react';

interface IProps {
  customMessage?: string | null;
}
 // custom message is not necessary, if it is not given then a common message will be shown
const EmptyHint = ({ customMessage = null }: IProps) => (
  <div>
    <div>{customMessage ? customMessage : 'common message for no data found '}</div>
  </div>
);

export default EmptyHint;
errorBoundary.tsx
import React from 'react';

interface IState {
  error: any;
  info: any;
}

interface IProps {
  errorMessage: any | null;
}

// also it has componentDidCatch method
class ErrorBoundary extends React.Component<IProps, IState> {
  constructor(props) {
    super(props);
    this.state = {
      error: null,
      info: null,
    };
  }

  componentDidCatch(error, info) {
    this.setState({
      error: error,
      info: info,
    });
  }

  render() {
    if (this.state.error || this.props.errorMessage) {
      return (
        <div>
          <h1>...... :( </h1>
            {this.state.error && this.state.error.toString()}
            {this.props.errorMessage && this.props.errorMessage.toString()}
        </div>
      );
    }
    return this.props.children;
  }
}

export default ErrorBoundary;
loading.tsx
import React from 'react';

const Loading = () => (
    <div>Wait for loading....</div>
);

export default Loading;
example:
import React from 'react';

// define props, states vs. ...

class Example extends React.PureComponent {

  // ...
  // when you calling your api, you should set loading, error props.
  async componentDidMount() {
    const { setMyData,  setLoader, setError } = this.props;

    setLoader(true);
    const myData = await getMyData()
      .catch((error) => {
        setError(error);
      });
    setLoader(false);

    setMyData(myData);
  }

  render(){
    const { myData, isLoading, error } = this.props;
    // usually your main component gets data, sets error & loading cases. It has callback methods vs.
    // The component named with Body includes html, css stuffs.
    // and you should define your body component as following. (with wraping our HOC named as withRenderCtrl)
    // export default withRenderCtrl(ExampleBody);
    return(
        <ExampleBody
          myData={myData}
          isLoading={isLoading}
          errorMessage={error}
          isDataReady={myData && !myData.isEmpty()}
          customEmptyMessage="my custom empty data >> this props is not necessary"
        />
    );
  }
}

export default Example;
References: https://github.com/kenneth1003/react-render-ctrl

No comments:

Post a Comment