skip to Main Content

I recently migrated to Nextjs from React and the architecture of Nextjs is different and confusing for me. In my next application, I am trying to authorize the API CRUD via a token that is stored in the cookie. Inside the axiosInstance, I am trying to get the token but it is outputting undefined.

axiosInstance.ts

import axios from 'axios';
import Cookies from 'js-cookie';

const axiosInstance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_CLIENT_URL,
  withCredentials: true,
});

axiosInstance.interceptors.request.use((config) => {
  const token = Cookies.get('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

export default axiosInstance;

Here is the api function to get staff data.

staffAPIServices.ts

import axiosInstance from '../../axios/axiosInstance';
// Get Staff
export const getStaff = async () => {
  try {
    const response = await axiosInstance.get('admin/staff');
    if (response.data.status) {
      return response?.data?.data?.staff;
    }
  } catch (err: unknown) {
    const errorString = err instanceof Error ? err.message : 'Unknown Error';
    return err;
  }
};

To showcase the staff data inside a table I have written the following code:

import clsx from 'clsx';
import { getStaff } from '@/app/utils/services/staff/staffAPIServices';


export default async function StaffTable({
  query,
  page,
}: {
  query: string;
  page: number;
}) {

  interface ItemType {
    id: number;
    name: string;
    email: string;
    email_verified_at: string;
    created_at: string;
    updated_at: string;
    deleted_at: string;
  }

  const staff = await getStaff();

  return (
    <div className="mt-6 flow-root">
      <div className="inline-block min-w-full align-middle">
        <div className="rounded-lg bg-gray-50 p-2 md:pt-0">
          <table className="hidden min-w-full text-gray-900 md:table">
            <thead className="rounded-lg text-left text-sm font-normal">
              <tr>
                <th scope="col" className="px-4 py-5 font-medium sm:pl-6">
                  ID
                </th>
                <th scope="col" className="px-3 py-5 font-medium">
                  Name
                </th>
                <th scope="col" className="px-3 py-5 font-medium">
                  Email
                </th>
                <th scope="col" className="px-3 py-5 font-medium">
                  Date
                </th>
                <th scope="col" className="px-2 py-5 font-medium">
                  Status
                </th>
                <th scope="col" className="relative py-3 pl-6 pr-3">
                  <span className="sr-only">Edit</span>
                </th>
              </tr>
            </thead>
            <tbody className="bg-white">
              {Array.isArray(staff) &&
                staff.map((item: ItemType) => (
                  <tr
                    key={item.id}
                    className="w-full border-b py-4 text-sm last-of-type:border-none [&:first-child>td:first-child]:rounded-tl-lg [&:first-child>td:last-child]:rounded-tr-lg [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg"
                  >
                    <td className="whitespace-nowrap py-3 pl-6 pr-3">
                      <p>{item.id}</p>
                    </td>
                    <td className="whitespace-nowrap px-3 py-4">{item.name}</td>
                    <td className="whitespace-nowrap px-3 py-4">
                      {item.email}
                    </td>
                    <td className="whitespace-nowrap px-3 py-4">
                      {item.created_at}
                    </td>
                    <td className="whitespace-nowrap px-3 py-4">
                      <span
                        className={clsx(
                          'flex items-center justify-center rounded-full px-4 py-1',
                          {
                            'bg-red-500 text-white': item.deleted_at === null,
                            'bg-green-500 text-white': item.deleted_at !== null,
                          },
                        )}
                      >
                        {item.deleted_at ? 'Active' : 'Inactive'}
                      </span>
                    </td>
                  </tr>
                ))}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
}

So far I have tried the following:

  1. Pasting the token string directly in the Bearer fake-token-12312312(which is working) but this violates DRY principles and I have more than 100 API functions.
  2. Converting the axiosInstance.ts and staffAPIServices.ts into client-side components('use client'). This didn’t work!
  3. In another StackOverflow question someone suggested getting a cookie inside the page by converting into a client-side component but next is complaining Prevent client components from being async functions.

2

Answers


  1. Chosen as BEST ANSWER

    I found the solution. By converting the StaffTable into a client component, the API is successfully getting the stored token cookie and the response data is getting back. However, I don't know whether this is a professional Nextjs approach.

    'use client';
    
    import { useEffect, useState } from 'react'; 
    import clsx from 'clsx'; 
    import { getStaff } from '@/app/utils/services/staff/staffAPIServices';
    
    export default function StaffTable({   query,   page, }: {   query: string;   page: number; }) {   interface ItemType {
        id: number;
        name: string;
        email: string;
        email_verified_at: string;
        created_at: string;
        updated_at: string;
        deleted_at: string;   }
    
      const [staff, setStaff] = useState<ItemType[]>([]);   const [loading, setLoading] = useState(true);   const [error, setError] = useState('');
    
      useEffect(() => {
        const fetchStaff = async () => {
          try {
            const staffData = await getStaff();
            setStaff(staffData);
            setLoading(false);
          } catch (error) {
            setError('Error fetching staff data');
            setLoading(false);
          }
        };
    
        fetchStaff();   }, []);
    
      if (loading) {
        return <p>Loading...</p>;   }
    
      if (error) {
        return <p>{error}</p>;   }
    
      return (
        <div className="mt-6 flow-root">
          <div className="inline-block min-w-full align-middle">
            <div className="rounded-lg bg-gray-50 p-2 md:pt-0">
              <table className="hidden min-w-full text-gray-900 md:table">
                <thead className="rounded-lg text-left text-sm font-normal">
                  <tr>
                    <th scope="col" className="px-4 py-5 font-medium sm:pl-6">
                      ID
                    </th>
                    <th scope="col" className="px-3 py-5 font-medium">
                      Name
                    </th>
                    <th scope="col" className="px-3 py-5 font-medium">
                      Email
                    </th>
                    <th scope="col" className="px-3 py-5 font-medium">
                      Date
                    </th>
                    <th scope="col" className="px-2 py-5 font-medium">
                      Status
                    </th>
                    <th scope="col" className="relative py-3 pl-6 pr-3">
                      <span className="sr-only">Edit</span>
                    </th>
                  </tr>
                </thead>
                <tbody className="bg-white">
                  {Array.isArray(staff) &&
                    staff.map((item: ItemType) => (
                      <tr
                        key={item.id}
                        className="w-full border-b py-4 text-sm last-of-type:border-none [&:first-child>td:first-child]:rounded-tl-lg [&:first-child>td:last-child]:rounded-tr-lg [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg"
                      >
                        <td className="whitespace-nowrap py-3 pl-6 pr-3">
                          <p>{item.id}</p>
                        </td>
                        <td className="whitespace-nowrap px-3 py-4">{item.name}</td>
                        <td className="whitespace-nowrap px-3 py-4">
                          {item.email}
                        </td>
                        <td className="whitespace-nowrap px-3 py-4">
                          {item.created_at}
                        </td>
                        <td className="whitespace-nowrap px-3 py-4">
                          <span
                            className={clsx(
                              'flex items-center justify-center rounded-full px-4 py-1',
                              {
                                'bg-red-500 text-white': item.deleted_at === null,
                                'bg-green-500 text-white': item.deleted_at !== null,
                              },
                            )}
                          >
                            {item.deleted_at ? 'Active' : 'Inactive'}
                          </span>
                        </td>
                      </tr>
                    ))}
                </tbody>
              </table>
            </div>
          </div>
        </div>   
    ); 
    }
    

  2. axiosInstance.ts and staffAPIServices.ts are not React Components so adding 'use client' will not do anything. You need to make the components that uses these functions client-side not the functions itself.

    If those components are client-side and you checked that it runs in browser environment (put some console.logs and see if it’s printing to terminal or browser console) you can check the cookie itself:

    Cookies can have HttpOnly attributes set to them, in that case you can’t access them in client side. You can check that on application tab in Chrome dev tools:
    a cookie example with HttpOnly attribute

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search