djangodjango-viewssveltesveltekitsvelte-component

Conditional Rendering in Svelte Based on Server's Response


I am building a web application with Django as the server and Sveltekit on the client.

During the registration process, a link with a token is sent from the server to the user's provided email, when they visit their mailbox and click on the link they are redirected to the SvelteKit's domain.

Now, here is the problem - the client sends the token to the server for validation, and on the server I have a view that validates the token to see if it is valid or expired and sends the appropriate response to the client. Now, depending on the response received, I want to conditionally render out the right block of code to the user. If the server says the token is valid, they can finish their registration, otherwise, they need to resubmit their email for another token to be sent.

The issue I have seems to be with my SvelteKit and not my server.

This is my views.py -

@method_decorator(csrf_exempt, name='dispatch')
class ValidateTokenView(View):
    def get(self, request, token, *args, **kwargs):
        try:
            # Check if the token exists in the database
            token_instance = EmailConfirmationToken.objects.filter(token=token).first()
            print(f"Token received: {token}")
            print(f"Token instance retrieved: {token_instance}")

            if token_instance:
                print(f"Token created at: {token_instance.created_at}")
                is_expired = token_instance.is_expired()
                print(f"Is token expired? {is_expired}")

                if not is_expired:
                    # If the token is valid and not expired, respond with success
                    return JsonResponse({'valid': True, 'message': 'Token is valid'})
                else:
                    # If the token is expired, respond with failure
                    return JsonResponse({'valid': False, 'message': 'Token is expired'}, status=400)
            else:
                # If the token instance is not found
                return JsonResponse({'valid': False, 'message': 'Token is invalid or expired'}, status=400)
        except Exception as e:
            print(f'Unexpected error: {str(e)}')
            return JsonResponse({'error': 'Internal server error'}, status=500)

So, based on the print statements I wrote to track the flow of execution, a valid token is correctly regarded as valid and the Json response for this is sent to the client.

In my SvelteKit, I have routes > confirm-email > +page.js and +page.svelte

In my +page.js I have -

export async function load({ url }) {
    const token = url.searchParams.get('token');
    console.log('Token:', token);
  
    if (!token) {
        return { props: { valid: false } };
    }
  
    const res = await fetch(`http://localhost:8000/validate-token/${token}`);
    console.log('Fetch Response:', res); 
  
    if (res.ok) {
        const data = await res.json();
        console.log('Response Data:', data); 
        return { props: { valid: data.valid, formData: data.data } };
    } else {
        return { props: { valid: false } };
    }
}

My Browser console says -

Fetch Response: Response {type: 'cors', url: 'http://localhost:8000/validate-token/Eg7t0r2GEbTeSIQ3qvk7aiAhhVwYA3Pi/', redirected: true, status: 200, ok: true, …}
+page.js:14 Response Data: {valid: true, message: 'Token is valid'}

So, the correct response is indeed being received from the server.

This is my +page.svelte -

<script>
  export let data; // Import the data prop

  let { valid, data: formData } = data; // Destructure the data prop
  let email = '';
  let emailError = '';
  let csrfToken = '';
  let emailExistsError = '';
  let loading = false; 

  async function handleContinue() {
    emailError = validateEmail(email);
    if (!emailError) {
      loading = true; 
      try {
        csrfToken = await fetchCsrfToken();
        const data = await checkEmail(email, csrfToken);

        if (data.exists) {
          emailExistsError = 'This email address is already registered.';
        } else {
          emailExistsError = ''; 
          alert(`A new magic link has been sent to ${email}.`);
          window.location.href = '/';
        }
      } catch (error) {
        console.error('Error:', error);
      } finally {
        loading = false;
      }
    }
  }
</script>

<!-- Preceding html -->
  {#if valid}
    <h1>Almost there!</h1>
    <p>Finish creating your account so that you can upgrade to membership.</p>
    <div class="input-container">
      <label>Your full name</label>
      <input type="text" value={formData.fullName} readonly />
    </div>
    <div class="input-container">
      <label>Your email</label>
      <input type="email" value={formData.email} readonly />
    </div>
    <button>Create account</button>
  {:else}
    <h1 class="expired-header">Your sign up link has expired</h1>
    <p class="expired-paragraph">Enter the email address associated with your account, and we’ll send a new magic link to your inbox.</p>
    <form on:submit|preventDefault={handleContinue}>
      <input
        type="email"
        name="email"
        placeholder="Your email"
        bind:value={email}
        class:invalid={emailError || emailExistsError}
        required
      />
      {#if emailError}
        <div class="error-message">{emailError}</div>
      {/if}
      {#if emailExistsError}
        <div class="error-message">{emailExistsError}</div>
      {/if}
      {#if loading}
        <div class="loading">Checking email... Please wait.</div>
      {/if}
      <button type="submit">Continue</button>
    </form>
  {/if}
</div>

So, as can be seen, my intention is to render out different blocks of code depending of if the response from the server showed a valid token or not, but even when the response from the server says the token is valid, the else block above is still rendered. The else block is always rendered.

I don't know where my implementation is going wrong. Is the data not getting imported properly in my +page.svelte?

Also, an observation I made is that when the page loads, it takes a few seconds before this is shown in my browser console -

Fetch Response: Response {type: 'cors', url: 'http://localhost:8000/validate-token/Eg7t0r2GEbTeSIQ3qvk7aiAhhVwYA3Pi/', redirected: true, status: 200, ok: true, …}
+page.js:14 Response Data: {valid: true, message: 'Token is valid'}

My thinking is that the page loads before a response is received from the server and so the else block renders out because Svelte did not know to render out the "# if valid" block on time. Is my thinking correct?

Also, something else I observed is that when I hover over the valid here "{#if valid}" in my Vs Code, I see "let valid: any"

Am I right in concluding that is because my program does not have enough information to infer the specific type of valid, thus pointing out a problem with the import in the +page.svelte?


Solution

  • In your load() function you should not encapsulate the data you return in a {props}

    changing my +page.js to -

    export async function load({ url }) {
        const token = url.searchParams.get('token');
    
        if (!token) {
            return { valid: false };
        }
      
        const res = await fetch(`http://localhost:8000/validate-token/${token}`);
      
        if (res.ok) {
            const data = await res.json();
            return { valid: data.valid, formData: data.data };
        } else {
            return { valid: false };
        }
    }
    

    And in my +page.svelte i had -

    export let data; // Import the data prop
    
    let { valid, formData } = data; // Destructure the data prop