0

I have categories and every category has subcategories, and i want to setState a dictionary like

{category1.name: [/*list of all subcategories*/], category2.name: [...]}

and i have

useEffect(()=>{
        Axios.get("http://localhost:3001/categories/get").then((p) => { /* return a list of categories */
            setCategories(p.data)
            let actual = {}
            for (let i = 0; i < p.data.length; i++) {
                Axios.get("http://localhost:3001/category/subcategories", { /* return all the subcategories of p.data[i], this is working! */
                params : {category : p.data[i].category}}).then((p) => {
                    actual = {...actual, [p.data[i].category]: p.data}
                    console.log(actual) /* 1 */
                })
            }
            console.log(actual) /* 2 */
        })
    }, [])

I already have the backend working, but when i console.log(actual) /* 2 */ its just a empty dictionary {}; and when i console.log(actual) /* 1 */ its not empty (has just 1 element), and i don't know when can i setState(actual), someone know how to do what i want?

  • Here you need to use promise, As your second console i.e `console.log(actual) /* 2 */` executed before the first one. So it prints an empty object. – Harsh Patel Sep 08 '21 at 04:01

3 Answers3

2

You can just use async/await to works like what you need.

EDIT (mentioned by @skyboyer):

When you do a network call all requests will go sequentially and loading will take N×time_to_load_one

Try this out.

useEffect(()=>{
        Axios.get("http://localhost:3001/categories/get").then(async (p) => { /* return a list of categories */
            setCategories(p.data)
            let actual = {}
            for (let i = 0; i < p.data.length; i++) {
                await Axios.get("http://localhost:3001/category/subcategories", { /* return all the subcategories of p.data[i], this is working! */
                params : {category : p.data[i].category}}).then((p) => {
                    actual = {...actual, [p.data[i].category]: p.data}
                    console.log(actual) /* 1 */
                })
            }
            console.log(actual) /* 2 */
        })
    }, [])
Harsh Patel
  • 6,334
  • 10
  • 40
  • 73
  • I think it's worth to mention that all request will go sequentially and loading will take N×time_to_load_one – skyboyer Sep 08 '21 at 05:56
0

This should probably get you on the right track

useEffect(() => {
    (async() => {
        const p = await Axios.get("http://localhost:3001/categories/get")
        setCategories(p.data)
        let actual = {}
        for (let i = 0; i < p.data.length; i++) {
            const x = await Axios.get("http://localhost:3001/category/subcategories", { /* return all the subcategories of p.data[i], this is working! */
                params: {
                    category: p.data[i].category
                }
            })

            actual = {...actual, [x.data[i].category]: x.data
            }
            console.log(actual) /* 1 */

        }
        console.log(actual) /* 2 */
    })();
}, [])

Also avoid reusing the same variable like you did with p it makes reading the code harder.

AshotN
  • 395
  • 4
  • 15
0

It seems to me that you need to spend time learning about promise or async/await in JS. Forgive me that I cannot spend time writing a whole article to teach everything, please google these keywords.

To complement @HarshPatel's answer which uses async/await, below is a solution using promise. His solution makes requests sequentially, while mine does it concurrently.

useEffect(() => {
    Axios.get("http://localhost:3001/categories/get").then((p) => {
        /* return a list of categories */
        setCategories(p.data)

        const tasks = p.data.map((category) => {
            return Axios.get(
                "http://localhost:3001/category/subcategories",
                { params: { category } }
            ).then((p) => {
                return { [category]: p.data }
            })
        })
        
        Promise.all(tasks).then(results => {
            const actual = results.reduce((acc, item) => {
                return { ...acc, ...item }
            }, {})
        })

        setState(actual)
    })
}, [])
hackape
  • 18,643
  • 2
  • 29
  • 57