Как загрузить AJAX и обновить состояние в componentDidUpdate? - PullRequest
0 голосов
/ 24 сентября 2018

У меня есть компонент, который отображает список кредитных карт.Он получает контракт в качестве реквизита.Идентификатор контракта может иметь несколько связанных кредитных карт.Этот список извлекается из API через AJAX.Компонент может быть видимым или скрытым на экране.

Я получаю список кредитных карт через AJAX в состоянии cards, которое является кортежем (ключ / значение): [number, ICreditCardRecord []].Таким образом, я могу отслеживать, к какому контракту относится тот список, к которому у меня есть штат.

Таким образом, в componentDidUpdate я проверяю, не скрыт ли компонент и не соответствует ли выбранный контракт тому, для которого я сохранил список.Если нет, я снова получу список для нового контракта.

Проблема в том, что запускается бесконечный цикл.Я читал, что невозможно установить setState внутри componentDidUpdate, так как это вызывает повторную визуализацию, которая снова вызывает функцию, которая ... (бесконечный цикл).

Так как я могу это сделать?Кстати, я также использую состояние, чтобы определить, показывать ли оверлей загрузки или нет (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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...