У меня есть компонент, который отображает список кредитных карт.Он получает контракт в качестве реквизита.Идентификатор контракта может иметь несколько связанных кредитных карт.Этот список извлекается из 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)