如何在componentDidUpdate中加载AJAX和更新状态?

时间:2018-09-24 20:54:25

标签: ajax reactjs typescript

我有一个呈现信用卡列表的组件。它收到合同作为支柱。合同ID可以具有多个相关的信用卡。该列表是通过AJAX从API获取的。该组件可以在屏幕上可见或隐藏。

我通过状态为cards的AJAX提取了信用卡列表,该列表是一个元组(键/值):[number,ICreditCardRecord []]。这样,我就可以跟踪状态列表属于哪个合同ID。

因此,在componentDidUpdate中,我检查组件是否未隐藏以及所选合同是否与我保存列表的合同匹配。如果没有,我将再次获取新合同的清单。

问题是,它触发了无限循环。我读到componentDidUpdate内的setState是不可能的,因为它会触发重新渲染,从而再次触发函数……(无限循环)。

那我该怎么做呢?顺便说一句,我还使用状态来管理是否显示加载叠加层(isLoading:布尔值)。每当我开始/停止加载时,我都会更新此状态道具。

import React, { Component } from 'react'
import { IApiVehicleContract, ICreditCardRecord } from '@omnicar/sam-types'
import * as api from 'api/api'
import { WithStyles, withStyles } from '@material-ui/core'
import CreditCard from 'components/customer/Contract/Details/CreditCard'
import NewCreditCard from 'components/customer/Contract/Details/NewCreditCard'
import AddCardDialog from 'components/customer/Contract/Details/AddCardDialog'
import { AppContext } from 'store/appContext'
import LoadingOverlay from 'components/LoadingOverlay'
import styles from './styles'
import alertify from 'utils/alertify'
import { t } from '@omnicar/sam-translate'
import { loadScript } from 'utils/script'

interface IProps extends WithStyles<typeof styles> {
  contract: IApiVehicleContract | undefined
  hidden: boolean
}

interface IState {
  cards: [number, ICreditCardRecord[]]
  showAddCreditCardDialog: boolean
  isStripeLoaded: boolean
  isLoading: boolean
}

class CustomerContractDetailsTabsCreditCards extends Component<IProps, IState> {
  public state = {
    cards: [0, []] as [number, ICreditCardRecord[]],
    showAddCreditCardDialog: false,
    isStripeLoaded: false,
    isLoading: false,
  }

  public componentDidUpdate(prevProps: IProps, prevState: IState) {
    const { cards } = this.state
    const { contract, hidden } = this.props

    // selected contract changed
    const changed = contract && contract.serviceContractId !== cards[0]

    // visible and changed
    if (!hidden && changed) {
      this.getCreditCards()
      console.log('load')
    }
  }

  public async componentDidMount() {
    // load stripe
    const isStripeLoaded = await loadScript('https://js.stripe.com/v3/', 'stripe-payment')
    this.setState({ isStripeLoaded: !!isStripeLoaded })
  }

  public render() {
    const { classes, contract, hidden } = this.props
    const { cards, showAddCreditCardDialog, isLoading } = this.state

    return (
      <div className={`CustomerContractDetailsTabsCreditCards ${classes.root} ${hidden && classes.hidden}`}>
        <React.Fragment>
          <ul className={classes.cards}>
            {cards[1].map(card => (
              <CreditCard
                className={classes.card}
                key={card.cardId}
                card={card}
                onDeleteCard={this.handleDeleteCard}
                onMakeCardDefault={this.handleMakeDefaultCard}
              />
            ))}
            <NewCreditCard className={classes.card} onToggleAddCreditCardDialog={this.toggleAddCreditCardDialog} />
          </ul>

          <AppContext.Consumer>
            {({ stripePublicKey }) => {
              return (
                <AddCardDialog
                  open={showAddCreditCardDialog}
                  onClose={this.toggleAddCreditCardDialog}
                  contract={contract!}
                  onAddCard={this.handleAddCard}
                  stripePublicKey={stripePublicKey}
                  isLoading={isLoading}
                />
              )
            }}
          </AppContext.Consumer>
        </React.Fragment>
        <LoadingOverlay open={isLoading} />
      </div>
    )
  }

  private getCreditCards = async () => {
    const { contract } = this.props

    if (contract) {
      // this.setState({ isLoading: true })
      const res = await api.getContractCreditCards(contract.prettyIdentifier)
      debugger
      if (res) {
        if (res.errorData) {
          // this.setState({ isLoading: false })
          alertify.warning(t('A problem occured. Please contact OmniCar If the problem persists...'))
          console.error(res.errorData.message)
          return
        }

        // sort list: put active first
        const creditCards: ICreditCardRecord[] = res.data!.sort((a, b) => +b.isDefault - +a.isDefault)
        const cards: [number, ICreditCardRecord[]] = [contract.serviceContractId, creditCards]
        debugger
        this.setState({ cards, isLoading: false })
      }
    }
  }

  private handleDeleteCard = async (cardId: string) => {
    const { contract } = this.props

    // show spinner
    this.setState({ isLoading: true })

    const req = await api.deleteCreditCard(contract!.prettyIdentifier, cardId)

    if (req && (req.errorData || req.networkError)) {
      alertify.warning(t('A problem occured. Please contact OmniCar If the problem persists...'))

      // hide spinner
      this.setState({ isLoading: false })

      return console.error(req.errorData || req.networkError)
    }

    // remove card from list
    const creditCards = this.state.cards[1].filter(card => card.cardId !== cardId)
    const cards: [number, ICreditCardRecord[]] = [this.state.cards[0], creditCards]

    // update cards list + hide spinner
    this.setState({ isLoading: false, cards })

    // notify user
    alertify.success(t('Credit card has been deleted'))
  }

  private handleMakeDefaultCard = async (cardId: string) => {
    const { contract } = this.props

    // show spinner
    this.setState({ isLoading: true })

    const req = await api.makeCreditCardDefault(contract!.prettyIdentifier, cardId)
    if (req && (req.errorData || req.networkError)) {
      alertify.warning(t('A problem occured. Please contact OmniCar If the problem persists...'))

      // hide spinner
      this.setState({ isLoading: false })

      return console.error(req.errorData || req.networkError)
    }

    // show new card as default
    const creditCards = this.state.cards[1].map(card => {
      const res = { ...card }
      if (card.cardId !== cardId) {
        res.isDefault = false
      } else {
        res.isDefault = true
      }
      return res
    })
    const cards: [number, ICreditCardRecord[]] = [this.state.cards[0], creditCards]

    // update cards list + hide spinner
    this.setState({ isLoading: false, cards })

    // notify user
    alertify.success(t('Credit card is now default'))
  }

  private toggleAddCreditCardDialog = () => {
    this.setState({ showAddCreditCardDialog: !this.state.showAddCreditCardDialog })
  }

  private appendNewCardToList = (data: any) => {
    const {cards: cardsList} = this.state

    let creditCards = cardsList[1].map(card => {
      return { ...card, isDefault: false }
    })
    creditCards = [data, ...creditCards]
    const cards: [number, ICreditCardRecord[]] = [cardsList[0], creditCards]
    this.setState({ cards })

    // notify user
    alertify.success(t('Credit card has been added'))
  }

  private handleAddCard = (stripe: stripe.Stripe) => {
    // show spinner
    this.setState({ isLoading: true })

    const stripeScript = stripe as any
    stripeScript.createToken().then(async (result: any) => {
      if (result.error) {
        // remove spinner
        this.setState({ isLoading: false })

        // notify user
        alertify.warning(t('An error occurred... Please contact OmniCar if the problem persists. '))
        return console.error(result.error)
      }

      // add credit card
      const prettyId = this.props.contract ? this.props.contract.prettyIdentifier : ''
      const req = await api.addCreditCard({ cardToken: result.token.id, isDefault: true }, prettyId)
      if (req.errorData) {
        alertify.warning(t('An error occurred while trying to add credit card'))
        return console.error(req.errorData)
      }

      // remove spinner and close dialog
      this.setState({ isLoading: false }, () => {
        this.toggleAddCreditCardDialog()
        this.appendNewCardToList(req.data)
      })
    })
  }
}

export default withStyles(styles)(CustomerContractDetailsTabsCreditCards)

0 个答案:

没有答案