John
John

Reputation: 1773

fetch rest api in svelte onmount is happening after the screen is rendered

Using the onMount photo example in the svelte docs, I can use my own rest api (yet another todo ..) to display data. I changed it to put the photo html in a separate component and it works. Here is App.svelte

    <script>
        import { onMount } from 'svelte'
        import Photos from './Photos.svelte'
    
        let photos = []
    
        onMount(async () => {
            const res = await fetch('http:///mint20-loopback4:3000/todos');
            photos = await res.json();
            console.log('todolist : ', photos)
        })
    </script>
    
    <Photos bind:photos={photos}/>

I then took the MDN svelte tutorial and went through that. Later, I removed the todo initial list from the store and added an onMount fetch in App.svelte. The result is an empty todo list but when I interact with the app, the data is displayed.

Here is the console output

Todos.svelte with length of :  0 
into TodoStatus.svelte :  Array []
totalTodos :  undefined 
completedTodos :  undefined 
Todos length - mounted :  0 
todolist :  Array(7) [ {…}, {…}, {…}, {…}, {…}, {…}, {…} ]
completed mount :  Array(7) [ {…}, {…}, {…}, {…}, {…}, {…}, {…} ]```

And App.svelte

<script lang="ts">
    import Todos from './components/Todos.svelte'
    import Alert from './components/Alert.svelte'
    import { onMount } from 'svelte'
    import type { TodoType } from './types/todo.type'
    
    let initialTodos: TodoType[] = []
    
    onMount(async () => {
        const res = await fetch('http:///mint20-loopback4:3000/todos');
        const json = await res.json();
        console.log('todolist : ', json)
        json.forEach(element  => {
          let t: TodoType = { id: 0, title: '0', isComplete: true }
          t.id = element.id
          t.title = element.title
          if (element.isComplete === undefined) {
            t.isComplete = false
          }
          else {
            t.isComplete = element.isComplete
          }
          initialTodos.push(t)
        })
        console.log('completed mount : ', initialTodos)
   })
</script>
      
 <Alert />
 <Todos bind:todos={initialTodos} />

So it does as expected in the photos example but not in this where the empty array is passed on instead before the onMount. The todo example has a lot more components than photos which just has the single component but, suffice to say, I am confused. What do I have to do get the initial screen to use the populated data?

Just to add - I included console logging in photos and that too has the same pattern - first child component logs and finally the onMount in App.svelte. And yet here, the data is correctly displayed.

So I think is supposed to happen is that because changes to the array are dynamically reflected in the display, when the array is updated, so is the display. But in todos this is not happening.

Upvotes: 1

Views: 2428

Answers (1)

alexvdvalk
alexvdvalk

Reputation: 1234

The DOM is only updated on variable assignments. In your example, you're using the .push() method to add an object to the array. So you'll need to add this after your forEach loop to ensure the component gets the updates:

initialTodos = initialTodos;

Another option is to do this inline. You can replace:

initialTodos.push(t)

with

initialTodos = [...initialTodos,t]

Here is the official docs around this.

Upvotes: 1

Related Questions