ForEach not working in useEffect using React

Viewed 495

Context

  • The idea of the project is to have a drop-down menu (an Accordion in Material-UI) of the different Applications.
  • To have for each Application its API calls concerning it.
  • To have for each API its incoming and outgoing parameters.

I succeeded in having my list of applications and below each one the APIs concerning them but I block to recover for each API their parameters.

The display looks something like this:

Application n°1

-- APIs 1

-- -- Parameters

Being still learning React in general, I had trouble with the "API" retrieval.


Here is my code:

export default function documentationv2() {

    // Getteur - Setteur
    const [applis, setApplis] = useState([]);
    const [APIs, setAPIs] = useState({});
    const [parametres, setParametres] = useState({});


    // Chargement
    const loadRessources = () => {
        axios.get('/api/documentation/application')
        .then((response) => {
            setApplis(response.data);
        })
        .catch((error) => console.log(error));
    }
    
    // Liste des APIs pour une application
    const ListeAPI = (AppliID) => {
        axios.get('/api/documentation/api?app_id=' + AppliID)
        .then((response) => {
            setAPIs(prev => {
                return {
                    ...prev,
                    [AppliID]: response.data
                }
            })
        })
    }

    // Liste des paramètres pour une API
    const ListeParametre = (IdAPI) => {
        console.log('ID : ' + IdAPI)
        axios.get('/api/documentation/parametre?api_id=' + IdAPI)
        .then((response) => {
            setParametres(prev => {
                return {
                    ...prev,
                    [IdAPI]: response.data
                }
            })
        })
    }

    useEffect(() => {
        if (applis == "" ) {
            loadRessources();
        }
        else{
            console.log('Length Appli : ' + applis.length)
            console.log('Appli : ' + applis.toString())
            applis.length && applis.forEach((application) => ListeAPI(application.IDAPPLI));
        }
    }, [applis])

    useEffect(() => {
        console.log('Length API : ' + APIs.length)
        console.log('API : ' + APIs)
        // APIs.length && 
        APIs.length && Object.values(APIs).forEach((api) => ListeParametre(api.api_id));
    }, [APIs])


    return (
        <Grid container className={classes.root}>
            {/* -- BODY -- */}
            <Grid item className={classes.Body}>
                <Grid container className={classes.GridContainerBody}>
                    {applis.map((application, index) => (
                        <div className={classes.DivContainerBody}>
                            <Grid item className={classes.GridItemLeftBody}>
                                <Accordion key={application.IDAPPLI} className={classes.AccordionBody}>
                                    <AccordionSummary expandIcon={<IconButton className={classes.ButtonBodyShow} disableRipple={true}><ExpandMoreIcon/></IconButton>}>
                                        <Typography>
                                            {application.NOMAPPLI} - ({application.NBAPI})
                                        </Typography>
                                    </AccordionSummary>
                                    <AccordionDetails className={classes.AccordionDetailsApplication}>
                                        {APIs[application.IDAPPLI] && APIs[application.IDAPPLI].map((api, index) => (
                                            <div className={classes.DivDetailsApplication}>
                                                <Accordion key={api.api_id} className={classes.AccordionBody}>
                                                    <AccordionSummary expandIcon={<IconButton className={classes.ButtonBodyShow} disableRipple={true}><ExpandMoreIcon/></IconButton>}>
                                                        <div className={classes.AccordionSumAPI}>
                                                            <Typography>
                                                                {api.api_uri}
                                                            </Typography>
                                                            <Typography>
                                                                {api.api_description}
                                                            </Typography>
                                                            <div className={classes.AccordionSumAPIDiv}>
                                                                <Chip
                                                                    style={{backgroundColor: "#06d6a0", width: "fit-content"}}
                                                                    label={api.api_version}
                                                                />
                                                                <Chip
                                                                    style={{backgroundColor: "#ffd166", width: "fit-content"}}
                                                                    label={api.api_response_type}
                                                                />
                                                            </div>
                                                            <div>
                                                                <Typography>
                                                                    Créé par {api.api_creation_user} le {api.api_date_creation}.
                                                                </Typography>
                                                                <Typography>
                                                                    Modifié par {api.api_maj_user} le {api.api_date_maj}.
                                                                </Typography>
                                                            </div>
                                                        </div>
                                                    </AccordionSummary>
                                                    <AccordionDetails className={classes.GridContainerBody}>
                                                    {parametres[api.api_id] && parametres[api.api_id].map((parametre, index) => (
                                                        <div key={index}>
                                                            <Typography>
                                                                Paramètre(s) d'entrée :
                                                            </Typography>
                                                            <Divider className={classes.ParamDivider}/>
                                                            <Chip
                                                                style={{backgroundColor: "#06d6a0", width: "fit-content"}}
                                                                label={parametre.INPUT}
                                                            />
                                                            <Typography style={{marginTop: "1%"}}>
                                                                Paramètre(s) de sortie :
                                                            </Typography>
                                                            <Divider className={classes.ParamDivider}/>
                                                            <Chip
                                                                style={{backgroundColor: "#ffd166", width: "fit-content"}}
                                                                label={parametre.OUTPUT}
                                                            />
                                                        </div>
                                                    ))}
                                                    </AccordionDetails>
                                                </Accordion>
                                            </div>
                                        ))}
                                    </AccordionDetails>
                                </Accordion>
                            </Grid>

                            <Grid item className={classes.GridItemRightbody}>
                                <div>
                                    <Tooltip title="Mot de passe" placement="top" arrow>
                                        <IconButton 
                                            className={classes.ButtonHeader}
                                            disableRipple={true}
                                        >
                                            <LockIcon/>
                                        </IconButton>
                                    </Tooltip>
                                </div>
                                <div>
                                    <Tooltip title="Télécharger doc
                                    " placement="top" arrow>
                                        <IconButton 
                                            className={classes.ButtonHeader}
                                            disableRipple={true}
                                        >
                                            <GetAppIcon/>
                                        </IconButton>
                                    </Tooltip>
                                </div>
                            </Grid>
                        </div>
                    ))}
                </Grid>
            </Grid>
            
        </Grid>
    )
}

You can see that I tried to reproduce the same method for the parameters as on the APIs.

I could see that my const "ListeParametre" was not called, with Object.values(APIs) it is called (I understood that API is an object and that I had to convert it to an Array to be able to apply a forEach

APIs.length && Object.values(APIs).forEach((api) => ListeParametre(api.api_id));

But now no parameters are sent, giving an error with the Axios call

(console.log('ID : ' + IdAPI) is undefined)

Note: API.length seems to be undefined


EDIT 1 : Inside API via console.log(Oject.entries(APIs))

API : [["1", [{"api_id": IdValue, "api_uri": UriValue, "api_description": DescriptionValue, ...}, {...}]]]

EDIT 2 : When using the Object.entries on API it seems to go the first row of APIs for the first application. But then a 404 error comes in because api_id is undefined.

    useEffect(() => {
    Object.entries(APIs).length && Object.entries(APIs).forEach((api) => ListeParametre(api.api_id))
}, [APIs])

Can someone explain my error? Thanks for any help! If my problem/title is not clear, tell me please so I can edit it.

4 Answers

In your EDIT1: I saw the object was multiple arrays.

[["1", [{"api_id": IdValue, "api_uri": UriValue, "api_description": DescriptionValue, ...}, {...}]]]

I think you can try with this

APIs.length > 0 && APIs[0].length > 1 && APIs[0][1].length > 0 && APIs[0][1].forEach((api)=>ListeParametre(api.api_id))
if (applis == "" ) {
        loadRessources();
    }
    else{
        console.log('Length Appli : ' + applis.length)
        console.log('Appli : ' + applis.toString())
        applis.length && applis.forEach((application) => ListeAPI(application.IDAPPLI));
    }

Sorry, this peace of code doesnt make sense to me, maybe you can provide sandbox example with stubs in API calls?

Here you check if applis is empty string, but you set it by default to array, hence this statement will always be false unless you set applis to string, and only place you ever set applic is loadRessources function

Then in "else" statement you check if applis has length but it will always be empty array as function in which you set applis (loadRessources) is called in that if statement which never gonna be executed

Why are you calling .length on the object

because you have set the empty state an Object

const [APIs, setAPIs] = useState({});

and you are calling .length function on a object because it used only on array

I think that's why it never goes true it returns undefined every time

you can use like this:

Object.entries(API).length && Object.values(APIs).forEach((api) => ListeParametre(api.api_id));

I think here is the error:

 if (applis == "" ) {
            loadRessources();
        }

Inside loadRessources you are calling setApplis. You want to call loadRessources, if (applis == "" ) however the way you set the state is this.

 const [applis, setApplis] = useState([]);

So if statement should be

 if (applis === [] ) {
                loadRessources();
            }

You are setting APIs inside ListeAPI which is called under this condition:

 applis.length && applis.forEach((application) => ListeAPI(application.IDAPPLI));

Since you never called loadRessources, applis is not set and ListeAPI is not called. Since it is not called, setAPIs is never set because you are setting it inside ListeAPI.

Since APIs are not set APIs.length is falsy.

Related