-1

I have to admit, it the most strange bug that i saw in TypeScript-JavaScript (I got Model class in TypeScript and ReactJS Component in JS...) . I have a list of Promo Object, inside this one a property "_listCompte" which is a list of Compte Object.

The list of Promo is created by retrieving data from database, all works at this point

Console

After that i got a forEach on listPromo and it works, but when i try to iterate with forEach on a listCompte it's looks like is empty.

Console2

ReactJS Component :

class DettesView extends Component{
state = {
    loading: true,
    activeItem: "DI4",
    listPromo: []
};


componentDidMount() {
    let listPromo = [];
    FirebaseAPI.getDatabase().collection("Promo")
        .onSnapshot((docSnapshot)=>{
            docSnapshot.forEach((doc)=>{
                const promoData = doc.data();
                let promo = Promo.fromData({
                    nom: promoData.nom,
                    promoId: promoData.promoId
                });
                promoData.listCompte.forEach((compteRef)=>{
                    compteRef.get()
                        .then((doc)=>{
                            if(doc.exists){
                                let compte = Compte.fromData(doc.data());
                                promo.addCompte(compte);
                            }
                        }
                    )
                });
                listPromo.push(promo);
            });
            console.log("listPromo", listPromo);
            this.setState({
                loading: false,
                status: true,
                listPromo: listPromo
            });
        }, (error)=>{
            this.setState({
                loading: false,
                status: false,
                errorMessage: error.getMessage(),
                errorCode: error.getCode()
            });
        });
}

toggleTabs = tab => () => {
    if (this.state.activeTab !== tab) {
        this.setState({
            activeTab: tab
        });
    }
};

render() {
    const {loading, listPromo} = this.state;
    if(loading)
        return (
            <MDBContainer fluid>
                <MDBRow center={true}>
                    <MDBCol size="2" className="mt-5">
                        <MDBCard>
                            <MDBCardBody>
                                <img src={gifLoading} alt="gif-loading"/>
                                <h1 className="mt-4 text-center">Loading...</h1>
                            </MDBCardBody>
                        </MDBCard>
                    </MDBCol>
                </MDBRow>
            </MDBContainer>
        );
    else if(this.state.status)
        return (
            <div className="classic-tabs">
                <MDBNav classicTabs color="cyan">
                    {listPromo.map((value, index) => (
                        <MDBNavItem key={index}>
                            <MDBNavLink to="#" active={this.state.activeItem === value.nom} onClick={this.toggleTabs(value.nom)}>
                                {value.nom}
                            </MDBNavLink>
                        </MDBNavItem>
                    ))}
                </MDBNav>
                <MDBTabContent
                    className="card"
                    activeItem={this.state.activeItem}
                >
                    {listPromo.map((value, index) => {
                        console.log("value of property 'listCompte' in a 'Promo' object", value.listCompte);
                        console.log("size of property 'listCompte' in a 'Promo' object", value.listCompte.length);
                        return (
                            <MDBTabPane tabId={value.nom} key={index}>
                                <MDBTable hover>
                                    <MDBTableHead>
                                        <tr>
                                            <th>Prénom</th>
                                            <th>Nom</th>
                                            <th>Promo</th>
                                            <th>Dette</th>
                                        </tr>
                                    </MDBTableHead>
                                    <MDBTableBody>
                                        {value.listCompte.map((valueCompte, indexCompte) => {
                                            console.log("compte", valueCompte);
                                            return (
                                                <tr key={indexCompte}>
                                                    <td>{valueCompte.nom}</td>
                                                    <td>{valueCompte.prenom}</td>
                                                    <td>{value}</td>
                                                    <td>{valueCompte.dette}</td>
                                                </tr>
                                            )
                                        })}
                                    </MDBTableBody>
                                </MDBTable>
                            </MDBTabPane>
                        )
                    })}
                </MDBTabContent>
            </div>
        );
    else
        return (
            <MDBContainer fluid>
                <MDBRow center={true} className="mt-3">
                    <MDBCol size="6">
                        <MDBCard>
                            <MDBCardBody>
                                <img src={gifError} alt="gif-error" className="text-center img-fluid"/>
                                <h2>Erreur &#128551; : {`${this.state.errorCode}`}</h2>
                                <h4>=> {`${this.state.errorMessage}`}</h4>
                            </MDBCardBody>
                        </MDBCard>
                    </MDBCol>
                </MDBRow>
            </MDBContainer>
        );
}

}

(I already tried to use map instead of forEach but it didn't work). If someone got an idea, thanks

Promo TS-Class :

interface PromoData {
    promoId: string;
    nom: string;
}

class Promo {
private _nom: string;
private _promoId: string;
private _listCompte: Array<Compte>;

constructor(proId: string, nom: string) {
    this._nom = nom;
    this._promoId = proId;
    this._listCompte = new Array<Compte>();
}

get listCompte(): Array<Compte> {
    return this._listCompte;
}

addCompte = (value: Compte) => {
    this._listCompte.push(value);
};

get promoId(): string {
    return this._promoId;
}

set promoId(value: string) {
    this._promoId = value;
}

get nom(): string {
    return this._nom;
}

set nom(value: string) {
    this._nom = value;
}

static fromData(promoData: PromoData): Promo{
    return new this(
        promoData.promoId,
        promoData.nom
    );
}

logError() : void {
    console.log(this._listCompte);
    this._listCompte.forEach((compte: Compte)=>{
        console.log(compte)
    })
}

}

And Compte TS-Class

interface CompteData {
    promoId: string;
    nom: string;
    prenom: string;
    dette: number;
    isKefet: boolean;
    compteId: string;
}

class Compte {
private _nom: string;
private _prenom: string;
private _dette: number;
private _promoId: string;
private _isKefet: boolean;
private _compteId: string;

constructor(compteId: string, nom: string, prenom: string, dette: number, isKefet: boolean, promoId: string) {
    this._compteId = compteId;
    this._nom = nom;
    this._prenom = prenom;
    this._dette = dette;
    this._promoId = promoId;
    this._isKefet = isKefet;
}

get compteId(): string {
    return this._compteId;
}

set compteId(value: string) {
    this._compteId = value;
}

get nom(): string {
    return this._nom;
}

set nom(value: string) {
    this._nom = value;
}

get prenom(): string {
    return this._prenom;
}

set prenom(value: string) {
    this._prenom = value;
}

get dette(): number {
    return this._dette;
}

set dette(value: number) {
    this._dette = value;
}

get isKefet(): boolean {
    return this._isKefet;
}

set isKefet(value: boolean) {
    this._isKefet = value;
}

static fromData(compteData: CompteData): Compte{
    return new this(
        compteData.compteId,
        compteData.nom,
        compteData.prenom,
        compteData.dette,
        compteData.isKefet,
        compteData.promoId,
    )
}

}

(All of my properties got getter and setter)

------- Additional Test -------

By adding a console.log of listPromo in render

enter image description here

JustinMartinDev
  • 559
  • 2
  • 7
  • 23

1 Answers1

0

This looks like an async issue. You are retrieving the compteRefs asynchronously, but you are not waiting until they are retrieved before calling setState() in componentDidMount, and you are not calling setState() after they are finally retrieved.

Please try this. This should properly wait until all async requests have finished before calling setState():

preparePromo(doc) {
    const promoData = doc.data();

    let promo = Promo.fromData({
        nom: promoData.nom,
        promoId: promoData.promoId
    });

    return Promise.all(promoData.listCompte.map((compteRef) =>
        compteRef.get()
    )).then((comptes) => comptes
        .filter(compte => compte.exists)
        .forEach(compte => promo.addCompte(Compte.fromData(compte.data())))
    ).then(() => promo);
}

componentDidMount() {
    FirebaseAPI.getDatabase().collection("Promo")
        .onSnapshot((docSnapshot)=>{
            Promise.all(docSnapshot.map(this.preparePromo))
                .then((listPromo) => {
                    console.log("listPromo", listPromo);

                    this.setState({
                        loading: false,
                        status: true,
                        listPromo: listPromo
                    });
                });
        }, (error)=>{
            this.setState({
                loading: false,
                status: false,
                errorMessage: error.getMessage(),
                errorCode: error.getCode()
            });
        });
}

Please pardon if I have any mistmatched parentheses or anything. I have no way of testing this.

Also, if you are amenable to using async/await, this code can be made a bit cleaner:

async preparePromo(doc) {
    const promoData = doc.data();

    let promo = Promo.fromData({
        nom: promoData.nom,
        promoId: promoData.promoId
    });

    const comptes = await Promise.all(promoData.listCompte.map((compteRef) =>
        compteRef.get()
    ));

    comptes
        .filter(compte => compte.exists)
        .forEach(compte => promo.addCompte(Compte.fromData(compte.data())));

    return promo;
}

componentDidMount() {
    FirebaseAPI.getDatabase().collection("Promo")
        .onSnapshot(async (docSnapshot) = >{
            const listPromo = await Promise.all(docSnapshot.map(this.preparePromo));

            console.log("listPromo", listPromo);

            this.setState({
                loading: false,
                status: true,
                listPromo: listPromo
            });
        }, (error)=>{
            this.setState({
                loading: false,
                status: false,
                errorMessage: error.getMessage(),
                errorCode: error.getCode()
            });
        });
}
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • Ok it doesn't work, but i think it's indeed the good way. The line "await Promise.all(docSnapshot.map(this.preparePromo));" doesn't retrun an array (it's undefined return). Maybe an idea to fix it ? – JustinMartinDev Nov 22 '19 at 18:04
  • Just add `return` before `comptes.filter...` in `preparePromo` – mayakwd Nov 22 '19 at 18:43
  • @JustinMartinDev As mayakwd points out, it's probably the missing `return` that's the issue. I've added it now. – JLRishe Nov 23 '19 at 02:40
  • 1
    @mayakwd Thanks. I actually needed to return `promo`, since the `comptes.filter...` bit only has side-effects. – JLRishe Nov 23 '19 at 02:41
  • @JLRishe Thank you, it's works. I did not realize that I could have a problem of asyncronism. – JustinMartinDev Nov 23 '19 at 19:31