Svelte user registration issue with setting store value
Asked Answered
F

1

5

helo :)

I'm trying to register an user and after success, setContext to newly registered user and then navigate to home. Server properly responds and registers user, but when setContext is called i get the following error: "index.mjs:552 Uncaught (in promise) Error: Function called outside component initialization"

    <script>
    import { setContext } from 'svelte'

    async function handleRegistration(e) {
        let user = {
                    firstname: e.target.firstname.value,
                    lastname: e.target.lastname.value,
                }

                fetch('http://localhost:3001/api/auth/register', {
                    method: 'POST',
                    headers: {'Content-Type':'application/json'},
                    body: JSON.stringify(user)
                })
                .then(res => res.json())
                .then(res => {
                    if(res.accessToken) {
                        user.accessToken = res.accessToken
                        user.refreshToken = res.refreshToken
                        setContext('userData', user)
                        navigate("/", { replace: true })
                    }
                })

                updateContext(user)
            }
    }
</script>

<form class="registration" on:submit|preventDefault="{handleRegistration}">
</form>

What am I doing wrong?

Febricity answered 8/3, 2020 at 20:48 Comment(0)
B
12

setContext must be called synchronously during component initialization. That is, from the root of the <script> tag:

<script>
  import { setContext } from 'svelte'

  console.log('init')

  setContext(...) // OK

  setTimeout(() => {
    setContext(...) // Not OK (we're not synchronous anymore)
  }, 0)
<script>

<h1>My Svelte Component</h1>

This is mentioned in a little cryptic sentence in the docs:

Like lifecycle functions, this must be called during component initialisation.

Other lifecycle functions are onMount, onDestroy, etc. It is arguably less obvious that setContext is such a lifecycle method.

Edit

I just reread your question, and realized this really just answer half of it...

setContext / getContext can only be used once at component init, so how do you share your API result through context? Related: how would you share those API results if the call was made outside of a Svelte component, where setContext would be even more out of the question (and the API call would arguably be better located, for separation of concerns matters)?

Well, put a store in your context.

For example, with a writable store:

<script>
  import { getContext } from 'svelte'

  const userData = getContext('userData')

  function handleRegistration(e) {
    doSuperApiCall()
      .then(data => {
        userData.set(data)
        // or fancy:
        $userData = data
      })
      .catch(...)
  }
</script>
...

Put this store in context during the init of some higher wrapping component (the like of <App>):

<script>
  import { setContext } from 'svelte'
  import { writable } from 'svelte/store'

  const userData = writable(null)

  setContext('userData', userData)
</script>

<slot />

This way you can easily access your store with getContext from any child component of (say) <App>, and read to / write from it asynchrnously.

Baptism answered 8/3, 2020 at 21:9 Comment(1)
Searched a long time for this answer, thanks!Dreda

© 2022 - 2024 — McMap. All rights reserved.