re-rendering issues when using HOC for loader

2019-07-31 14:01发布

问题:

I am building an app where there is tons of pages and each page needs loader icon to show when the data is coming from server but has not arrived yet but no content is shown if there is no data at all. I could show this without using Higher Order Component(HOC) with no problem but doing so in every component is tedious. I want the loader code be reusable so that i can use this in every component wherever needed. But I am facing the problem.

If there is data, there is no issue. If there is no data at all, then the loader keeps loading and loading instead should show no content.

I had to use if (!this.props.size) inside componentDidMount to fetch the data because if i dont do this, the component will be re-rendering continuously and if i do this, the component will not re-render if there is data but if there is no data at all, the component gets re-rendered continuously with loading icon.

Here is the code

class Logs extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      show: false,
    };
  }
  componentDidMount() {
    // this.props.requestLogs();
    if (!this.props.logs.size) {
      this.props.requestLogs();
    }
  }

  //componentWillUnmount() {
    //document.body.removeEventListener("", this.props.requestLogs());
  //}


  renderLogs() {
    const { logs } = this.props;
    return logs.size > 0
      ? logs.valueSeq().map(log => {
          return (
            <div className="card" key={log.get("_id")}>
              <li className="row">
                <div className="col-md-6">
                  <a
                    onClick={() =>
                      this.handleDialog(log.get("_id"), log.get("error_stack"))}
                  >
                    {log.get("error_message")}
                  </a>
                </div>
                <div className="col-md-6 text-right">
                  <a
                    className="text-danger"
                    onClick={() => this.handleDelete(log.get("_id"))}
                  >
                    Delete
                  </a>
                </div>
              </li>
            </div>
          );
        })
      : <p>No Content</p>;
  }
  render() {
    const { logs, isRequesting } = this.props;
    return (
      <div className="container">
        <div className="row mg-btm-md">
          <div className="col-xs-6"> <h1>Logs</h1></div>
        </div>
        <ul className="list-group">
          {this.renderLogs()}
          {this.state.show ? this.props.dialog : null}
        </ul>
      </div>
    );
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(
  Loader("isRequesting")(Logs)
);


const Loader = prop => WrappedComponent => {
  return class Loader extends React.PureComponent {
    render() {
      return this.props[prop]
        ? <div className="earth-spinning">
            <img
              src={EarthSpinning}
              alt="spinner"
              style={{ margin: "0 auto" }}
            />
          </div>
        : <WrappedComponent {...this.props} />;
    }
  };
};

export default Loader;

complete code is here https://gist.github.com/SanskarSans/b30c085ffacf01c58b66f128096746cc

回答1:

I did something super simple, and there is surely places to improve. But it solved the issues you wanted, if you have a flag of "loadedData" you show the data, if not you show a loader.

link to code: https://codepen.io/tzookb/pen/VWdeyE

code:

class SomeComponent extends React.Component {
  render() {
    return <div>Im a basic component</div>;
  }
}

function withLoader(WrappedComponent) {
  return class extends React.Component {
    render() {
      if (this.props.loadedData) {
        return <WrappedComponent {...this.props} />;        
      } else {
        return <h1>Loading...</h1>;
      }
    }
  };
}

let SomeComponentWithLoader = withLoader(SomeComponent);


ReactDOM.render(<SomeComponentWithLoader loadedData={false} />, document.getElementById("app"));