Home > Enterprise >  How to handle dynamic array with svelte/store
How to handle dynamic array with svelte/store

Time:02-27

I would like to receive several data from different APIs and put it together in one big array. This array should be accessable for different components. The components should list the array, if all APIs are called and the data was collected.

I adapted the code from REPL and added a iteration, to call each API. The array will be filled, but the component is not showing the list. First, I thought its related to reactivity / updating arrays, but it also wont work.

import { writable } from 'svelte/store'
export const API_LIST = [
    "http://example1.com/api/",
    "http://example2.com/api/"
];

export function readAll() {
    const { subscribe, update, set } = writable([])
    
    return {
        subscribe,
        init: async () => {
            let alldata = [];
            API_LIST.forEach(async function(apiurl) {
                const res = await fetch(apiurl)
                if (!res.ok) throw new Error('Bad response')
                alldata.push( await res.json() );
                alldata = alldata;
            });
            set(alldata);
            return alldata;
        }
    }
}
export const api_store = readAll()

My component:

<script>
    import { api_store } from 'reader.js';
</script>

<div>
    {#await api_store.init()}
        <p>wait for reading...</p>
    {:then}
        <ul>
        {#each $api_store as item}
            <li>
                {item}
            </li>
        {/each}
        </ul>
    {:catch error}
        <p>Something went wrong: {error.message}</p>
    {/await}
</div>

After all APIs are called and the array is build up, the "wait for reading" disappears, but the list showed is empty. I can see the array content with {console.info($api_store)}, so the data is present. But why is it not showing up in my component list?

CodePudding user response:

When you use the .foreach() method and give it an async callback, the code executed the call to

set(alldata);

and

return alldata;

before alldata array was full. (That is because how NodeJS internally works - the callback functions will not be called until the call stack is empty)

You can test it by putting hard-coded, random values in the alldata array. You will see the random values will appear inside the browser.

If you will replace the .forach() call to regular for loop you will prevent the code to return before all calls to fetch() will be finished.

Try to replace your code to the following:

reader.js:

import { writable } from "svelte/store";
export const API_LIST = [
  "http://example1.com/api/",
  "http://example2.com/api/",
];

export function readAll() {
  const { subscribe, update, set } = writable([]);

  return {
    subscribe,
    init: async () => {
      let alldata = [];
      // API_LIST.forEach(async function (apiurl) {
      //   const res = await fetch(apiurl);
      //   if (!res.ok) throw new Error("Bad response");
      //   alldata.push(await res.json());
      //   alldata = alldata;
      // });
      for (let i = 0; i < API_LIST.length; i  ) {
        const res = await fetch(API_LIST[i]);
        if (!res.ok) throw new Error("Bad response");
        alldata.push(await res.json());
      }
      set(alldata);
      return alldata;
    },
  };
}
export const api_store = readAll();

Component.svelte:

<script>
  import { api_store } from "./reader.js";
</script>

<div>
  {#await api_store.init()}
    <p>wait for reading...</p>
  {:then}
    <ul>
      {#each $api_store as item}
        <li>
          {item}
        </li>
      {/each}
    </ul>
  {:catch error}
    <p>Something went wrong: {error.message}</p>
  {/await}
</div>

This should work :)

CodePudding user response:

Returning alldata from init() isn't necessary, it's nowhere used
Alternatively to the for loop waiting for all data could also be handled with Promise.all()
-> REPL

init: async () => {         
            const requests = API_LIST.map(async url => {
                const response = await fetch(url)
                if(response.ok) return await response.json()                        
                else throw new Error('bad response')
            })
            const fetchResults = await Promise.all(requests)
            set(fetchResults)
        }
  • Related