Ricky
Ricky

Reputation: 777

Async issue with React render happening before set.state happens

I am having some trouble dealing with an async issue. The render is happening before the state is set at getStepContent(0) causing me to lose access to the state's values when I pass it down a component (CartInfo). Any ideas?

class Cart extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      order: [],
      error: null,
      finished: false,
      stepIndex: 0
    };
  }

  componentWillMount() {
    Meteor.call("orders.getLastOrder", (error, response) => {
      if (error) {
        this.setState(() => ({ error: error }));
        console.log(error);
      } else {
        this.setState(() => ({ order: response }));
        console.log(this.state.order);
      }
    });
  }

  goBack = () => this.props.history.push("/shop");
  goCart = () => this.props.history.push("/cart");

  handleNext = () => {
    const { stepIndex } = this.state;
    this.setState({
      stepIndex: stepIndex + 1,
      finished: stepIndex >= 2
    });
  };

  handlePrev = () => {
    const { stepIndex } = this.state;
    if (stepIndex > 0) {
      this.setState({ stepIndex: stepIndex - 1 });
    }
  };

  getStepContent(stepIndex) {
    let { order } = this.state;

    switch (stepIndex) {
      case 0:
        while (!order) {
          return getStepContent(0);
        }
        return <CartInfo CartInfo={order} />;
      case 1:
        return "What is an ad group anyways?";
      case 2:
        return "This is the bit I really care about!";
      default:
        return "You're a long way from home sonny jim!";
    }
  }

  render() {
    const { finished, stepIndex, order } = this.state;
    const contentStyle = { margin: "0 16px" };

    return (
      <CartPage pageTitle="Cart" history goBack={this.goBack}>
        <div className="CartHomePage">
          <div style={{ width: "100%", maxWidth: 700, margin: "auto" }}>
            <Stepper activeStep={stepIndex}>
              <Step>
                <StepLabel>Confirm your order</StepLabel>
              </Step>
              <Step>
                <StepLabel>Where should we send it to?</StepLabel>
              </Step>
              <Step>
                <StepLabel>Enjoy!</StepLabel>
              </Step>
            </Stepper>
            <div style={contentStyle}>
              {finished
                ? <p>
                    <a
                      href="#"
                      onClick={event => {
                        event.preventDefault();
                        this.setState({ stepIndex: 0, finished: false });
                      }}
                    >
                      Click here
                    </a>{" "}
                    to reset the example.
                  </p>
                : <div>
                    {this.getStepContent(stepIndex)}
                    <div style={{ marginTop: 12 }}>
                      <FlatButton
                        label="Back"
                        disabled={stepIndex === 0}
                        onClick={this.handlePrev}
                        style={{ marginRight: 12 }}
                      />
                      <RaisedButton
                        label={stepIndex === 2 ? "Finish" : "Next"}
                        primary={true}
                        onClick={this.handleNext}
                      />
                    </div>
                  </div>}
            </div>
          </div>
          <div>
            {/* {order.map((item, i) => <div key={i}> {item.name}  
                      {item.price} {item.quantity}</div>)} */}
            {/* {this.state.order[0]} */}
          </div>
        </div>
      </CartPage>
    );
  }
}

export default Cart;

This is the component I am passing it on to

import React from "react";
import { withRouter } from "react-router";

const CartInfo = ({ CartInfo }) =>
  <div>
    {CartInfo[0].name}
  </div>;
export default withRouter(CartInfo);

This is the error code I am currently getting "CartInfo.jsx:6 Uncaught TypeError: Cannot read property 'name' of undefined at CartInfo"

Upvotes: 1

Views: 213

Answers (1)

Max
Max

Reputation: 1567

It looks like you are trying to access CartInfo[0].name before the data has been fetched, which throws an error. You can change CartInfo component to something like this:

const CartInfo = ({ CartInfo }) => {
 if (CartInfo[0]) {
   return(
     <div>
       {CartInfo[0].name}
     </div>;
    );
  }
}

This way the component will return null, then when the order data is fetched it will rerender and CartInfo[0] will not be undefined.

Another way to do this would be to use lodash _.get which returns undefined instead of throwing an error when you try to access properties of undefined.

Upvotes: 1

Related Questions