Home > front end >  React fetch every url of an array of urls in a json
React fetch every url of an array of urls in a json

Time:04-27

I had a json file like this:

[
  {
    "name": "google",
    "route": "/google",
    "url": "www.google.com"
  },
  {
    "name": "bing",
    "route": "/bing",
    "url": "www.bing.com"
  },
  {
    "name": "duckduckgo",
    "route": "/duckduckgo",
    "url": "www.duckduckgo.com"
  }
]

I wanted to fetch each url of this json file and show the status of the fetchs in a table. I had this code that worked:

  // Construct an object with the JSON data
  const [data, setData] = useState(jsonData)

  // We need to put the fetch call inside useEffect
  // otherwise the fetchData will be called over and over every time the state is updated
  useEffect(() => {
    let headers = new Headers()

    //headers.append('Content-Type', 'application/json')
    //headers.append('Accept', 'application/json')
    headers.append('Origin', 'http://localhost:8080')

    // Array with all the promises from each fetch
    // @r contains all the info about the fetch, we are going to use the r.status later
    // @i is the index of the url in the JSON file
    const promises = jsonData.map((url, i) => {
      return fetch(url.route, {
        mode: 'no-cors',
        headers: headers,
      }).then((r) => ({
        fetch: r,
        index: i,
      }))
    })

    Promise.all(promises)
      .then((result) => {
        // Iterates through each promise and replaces the status value from the JSON file
        // with the status value from the fetch
        const new_data = result.map((d) => {
          jsonData[d.index].status = d.fetch.status
          return jsonData[d.index]
        })
        setData(new_data)
      })
      .catch((error) => {
        console.log('Error: ', error)
      })
  }, [])

Now, I updated a bit my json file, it is a little bit more complicated:

[
  {
    "section": "Sonarqube",
    "img": "sonarqube.png",
    "urls": [
      {
        "name": "SonarQube",
        "route": "/sonarqube",
        "url": "https://sonarqube-enterprise.com"
      },
      {
        "name": "SonarQube1",
        "route": "/sonarqube",
        "url": "https://sonarqube-enterprise1.com"
      },
      {
        "name": "SonarQub2e",
        "route": "/sonarqube",
        "url": "https://sonarqube-enterprise2.com"
      }
    ]
  },
  {
    "section": "Twistlock",
    "img": "twistlock.png",
    "urls": [
      {
        "name": "Twistlock",
        "route": "/twistlock",
        "url": "https://twistlock.com"
      }
    ]
  }
]

I'm trying to change my code to read the urls array inside each section but I can't figure out how to make it work. Can you please help me. This is where I am for now:

  const [data, setData] = useState(jsonData)

  useEffect(() => {
    const promises = jsonData.map((section, s) => {
      return section.urls.map((url, i) => {
        return fetch(url.route, {
          mode: 'no-cors',
        }).then((r) => ({
          fetch: r,
          index: i,
          sectionId: s,
        }))
      })
    })

    Promise.all(promises)
    .then((result) => {
      const new_data = result.map((section) => {
        section.map((d) => {
          jsonData[d.sectionId].urls[d.index].status = d.fetch.status
          jsonData[d.sectionId].urls[d.index].statusText = d.fetch.statusText
          return jsonData[d.sectionId].urls[d.index]
        })
      })
      setData(new_data)
    })
    .catch((error) => {
      console.log('Error: ', error)
    })
  }, [])

I believe that I'm almost there but I really don't understand how to make this work :O can you help me please

CodePudding user response:

You return array of array in nested map nor promises in your jsonData.map

[
    [
        Promise { <pending> },
        Promise { <pending> },
        Promise { <pending> }
    ],
    [ Promise { <pending> } ]
]

You need to add .flat() to flatten array (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat)

const promises = jsonData.map((section, s) => {
    return section.urls.map((url, i) => {
        return fetch(url.route, {
            mode: 'no-cors',
        }).then((r) => ({
            fetch: r,
            index: i,
            sectionId: s,
        }))
    })
}).flat()

CodePudding user response:

Try like this. You can first loop the outer array and then do the Promises.all. And destructure the properties you want.

const jsonData = [ { section: "Sonarqube", img: "sonarqube.png", urls: [ { name: "SonarQube", route: "/sonarqube", url: "https://sonarqube-enterprise.com" }, { name: "SonarQube1", route: "/sonarqube", url: "https://sonarqube-enterprise1.com" }, { name: "SonarQub2e", route: "/sonarqube", url: "https://sonarqube-enterprise2.com" } ] }, { section: "Twistlock", img: "twistlock.png", urls: [ { name: "Twistlock", route: "/twistlock", url: "https://twistlock.com" } ] } ];

const App = () => {

  const [data, setData] = React.useState(jsonData);

  React.useEffect(() => {
    const promises = jsonData.map((section, s) => {
      return section.urls.map((url, i) => {
        return fetch(url.route, {
          mode: "no-cors"
        }).then((r) => ({
          fetch: r,
          index: i,
          sectionId: s
        }));
      });
    });

    promises.forEach((promises2) => {
      Promise.all(promises2).then((result) => {
        const copyJsonData = [...jsonData];
        result.forEach(
          ({
            sectionId,
            index,
            fetch: {
              status,
              statusText
            }
          }) => {
            copyJsonData[sectionId].urls[index].status = status;
            copyJsonData[sectionId].urls[index].statusText = statusText;
          }
        );
        setData(copyJsonData);
      });
    });
  }, []);

  console.log(jsonData);

  return <div></div>;
};


ReactDOM.render( < App / > , document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>

CodePudding user response:

I don't know why you complicate things, try to separate the code so it could be understandable and easy for you to read.

Anyway here is how I would do it.

const promises = [];
jsonData.map((section, s) => {
  section.urls.map((url, i) => {
    // create it here so the context gets in the right place.
    let data = {
      index: i,
      sectionId: s,
    }
    // use push is more understandable
    promises.push(fetch(url.route, {
      mode: 'no-cors',
    }).then((r) => ({ ...data, fetch: r })));
  })
})
  • Related