atinux / atinotes

An editable website with universal rendering on the edge.
https://notes.atinux.com
MIT License
152 stars 8 forks source link

Cleaner way using custom fetch #8

Open xdiegom opened 4 months ago

xdiegom commented 4 months ago

Hi @Atinux! Hope you are doing well.

I was wondering if there is an update about creating a custom fetch composable that adapts the current Nuxt version 3.10.X because I believe your blog post is a bit behind from current changes and right know the Nuxt vesion I am using is showing a warning that I haven't solved which is [nuxt] [useFetch] Component is already mounted, please use $fetch instead. See https://nuxt.com/docs/getting-started/data-fetching.

I have followed the videos that the community suggest in this nuxt issue but the warning persists.

Thanks!

atinux commented 4 months ago

Where do you use useAPI @xdiegom ?

This should be used only for fetching the initial data of the component

xdiegom commented 4 months ago

Hi @Atinux , thanks for your quick response. I appreciate 🙌🏼.

Specific installed nuxt version: 3.10.1

Basically I use it in components, plugins or middleware like documentation suggests when using useFetch. Because custom useApi composable uses useFetch():

useFetch is a composable meant to be called directly in a setup function, plugin, or route middleware. It returns reactive composables and handles adding responses to the Nuxt payload so they can be passed from server to client without re-fetching the data on client side when the page hydrates.

The warning shows up when using in the client side, for example a Login form action button I understood that the reason of creating a customFetch is to reuse it in different parts of the application.

This is my useApi composable adapted to the current nuxt 3 version I have installed

// composable/useApi.ts 
export async function useApi<T>(
  path: string,
  options: UseFetchOptions<T> = {},
  prefix: string = 'api'
) {
  const config = useRuntimeConfig();

  const apiUrl = config.public.baseURL + '/' + prefix

  const jwt = useCookie('AUTH-TOKEN', {
    sameSite: 'lax',
    secure: true
  });

  const headerAuthorization: {
    Authorization?: string
  } = {}

  if (jwt.value) {
    headerAuthorization.Authorization = `Bearer ${jwt.value}`
  }

  let headers: any = {
    accept: "application/json",
    referer: config.public.appUrl,
    ...(headerAuthorization ?? { headerAuthorization })
  }

  return useFetch(path, {
    baseURL: apiUrl,
    watch: false,
    ...options,
    headers: {
      ...headers,
      ...options?.headers
    },
  })
}

Example of use:

// composable/useAuthentication.ts 

interface Profile {
  id: string,
  username: string,
  name: string
}

export default function () {
  const profile = useState<Profile>(() => ({}));

  const login = async (username: string, password: string): Promise<boolean> => {
    interface LoginResponse {
      token: string
    }

    const { data, status } = await useApi<LoginResponse>('login', {
      body: {
        username,
        password
      },
      method: "POST"
    })

    if (status.value === 'success') {
      const token = useCookie('AUTH-TOKEN', {
        sameSite: "lax",
        secure: true
      })

      token.value = data.value.token;

      return true

    }

    return false;
  }

  const getProfile = async (): Promise<void> => {
    const { data } = await useApi<Profile>('profile')
    profile.value = data.value.data;
  }

  return { login, getProfile, profile }
}

‼️ Here is where the warning is showing, when clicking the button that triggers the handleSubmit() function

// BaseLoginForm.vue
<template>
    <form @submit.prevent="handleSubmit" class="w-full space-y-3 flex flex-col items-center px-8">
        <BaseInput class="w-full" label="username" input-id="username" v-model="username">
        </BaseInput>
        <BaseInput type="password" class="w-full" label="password" input-id="password" v-model="password">
        </BaseInput>

        <BaseButton type="submit" class="w-full">
            Login
        </BaseButton>
    </form>
</template>

<script lang="ts" setup>
const { login } = useAuthentication();
const username = ref();
const password = ref();

const handleSubmit = async () => {
      await login(username.value, password.value);
}
// middleware/auth.global.ts
export default defineNuxtRouteMiddleware(async (to, from) => {
    const { isLoggedIn, getProfile } = useAuthentication();

    if (process.server && !isLoggedIn.value) {
        await getProfile()
    }
})

Thanks again for your reply.

Regards, Diego