Next.js Discord

Discord Forum

Structure design question about data share

Answered
Chilean jack mackerel posted this in #help-forum
Open in Discord
Chilean jack mackerelOP
I have this spotify.ts file that contains all the methods and data needed to use their API. It looks like this

import { APIErrorReturn, SpotifyAPIReturn } from './../interfaces/spotifyInterfaces';
const SPOTIFY_WEB_API_HOST = "https://api.spotify.com/v1"

class SpotifyAPI {
    private _access_token: string;

    constructor(token?: string){
        this._access_token = token || "";
    }

    setAccessToken(token: string){
        console.log("✓ Setting access token: " + token)
        this._access_token = token
    }

    getAccessToken(){
        return this._access_token;
    }

    async getArtist(id: string){
        return await callRequest({endpoint: "/artists/" + id, access_token: this._access_token}) as SpotifyAPIReturn
    }

    // ... other functions

export const SpotifyAPIClient = new SpotifyAPI();
}


I'm exporting the SpotifyAPIClient variable to use this globally to use these functions. But now I have a problem. Whenever I'm setting an access token, it's not set up properly on the client side. I mean, if I call setAccessToken on the serverSide code, the access_token is not set while calling functions in the clientSide. I understand that this can be because I set the value inside serverSide and these file states are not the same, but when I'm trying to update the access_token inside the layout component that is client sided the effect is the same. Now my question is, how can I properly setup this kind of structure?
Answered by Chilean jack mackerel
//action.ts
'use server'

import { getCurrentServerSession } from "@/database/auth"
import { SpotifyAPIClient } from "@/database/spotify"

export async function getSpotifyAccessToken() {
    return SpotifyAPIClient.getAccessToken()
}

export async function setSpotifyAccessToken() {
    const b = await getCurrentServerSession()
    if(b)
            SpotifyAPIClient.setAccessToken(b.access_token)
    else
        SpotifyAPIClient.setAccessToken('')
    
}

and now instead of getting access_token by SpotifyAPIClient.getAccessToken() inside API request function I just call the const access_token = await getSpotifyAccessToken() and it works fine but only with this bug
View full answer

54 Replies

Chilean jack mackerelOP
well unfortunately it's not doing much, I mean the problem still occurs and access_token is not being set
i mean it's being set but the state is not the same across other files i think
export const SpotifyAPIClient = remember('SpotifyAPIClient', () => new SpotifyAPI());
I understand that it could not have the same access_token state comparing to clientside and serverside version but I don't know why the clientside set and clientside get but in different files are not the same
what do you mean clientside set and clientside get?
you set it with server action on client side?
Chilean jack mackerelOP
I mean, I have for example an layout component which is server sided because it's server component
//layout.tsx
export default async function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  async function applyAccessToken(){
    console.log('applying')
    const res = await getCurrentClientSession();
    if(res){
        SpotifyAPIClient.setAccessToken(res.access_token)
    }
  }

  await applyAccessToken()
  return (
    <NextAuthProvider>
        <html lang="en">
          <body className={inter.className}>{children}</body>
        </html>
    </NextAuthProvider>
  )


and I'm trying to set the access_token there, but while reading the access token inside the client component, the access_token is null because it has been set on the server and not on the client (i assume) but now my question is why while setting up the access_token on the client sided component, I can't get this access_token in the other client sided component, like the SpotifyAPIClient instance is getting resetted every time.
can you show the code where you set the token and get the token on client side?
Chilean jack mackerelOP
for example I have this NextAuthProvider file for auth but It's client sided so I can use it to set the access_token

//NextAuthProvider.tsx
"use client";

import { getCurrentClientSession } from "@/database/auth";
import { SpotifyAPIClient } from "@/database/spotify";
import { SessionProvider } from "next-auth/react";

type Props = {
    children?: React.ReactNode;
};

export const NextAuthProvider = ({ children }: Props) => {
    async function applyAccessToken(){
        console.log('applying')
        const res = await getCurrentClientSession();
        if(res){
            SpotifyAPIClient.setAccessToken(res.access_token)
        }
      }
    
      applyAccessToken()
    return (
        <SessionProvider>
            {children}
        </SessionProvider>
    )
};


I know it's not good idea to just call the function like that but It's for testing anyways. And now I have my other client component for calling SpotifyAPI with previous set access_token but it's not there anymore.

//Component.tsx
"use client";

import { SpotifyAPIClient } from '@/database/spotify';
import React, { useEffect, useState } from 'react'
import TopListBox from './TopListBox';

const TopStatsContainer = () => {
    const [myTopArtists, myTopArtistsSet] = useState([]);

    async function fetchOverviewStats(){
            const topArtists = SpotifyAPIClient.getMyTopArtists(3);
            myTopArtistsSet((await topArtists).data.items)
    }

    useEffect(() => {
        console.log(' - TopStatsContainer (useEffect): ', SpotifyAPIClient.getAccessToken())
        fetchOverviewStats()
    }, [])

    return (
        <div className='top__stats_container'>
            <h1>My overwiew</h1>
            <TopListBox title={"test"} data={myTopArtists} />
        </div>
    )
}


and of course while calling .getMyTopArtists() function I get the error because access_token is not set in this instance but I don't know why.
@Chilean jack mackerel for example I have this `NextAuthProvider` file for auth but It's client sided so I can use it to set the access_token tsx //NextAuthProvider.tsx "use client"; import { getCurrentClientSession } from "@/database/auth"; import { SpotifyAPIClient } from "@/database/spotify"; import { SessionProvider } from "next-auth/react"; type Props = { children?: React.ReactNode; }; export const NextAuthProvider = ({ children }: Props) => { async function applyAccessToken(){ console.log('applying') const res = await getCurrentClientSession(); if(res){ SpotifyAPIClient.setAccessToken(res.access_token) } } applyAccessToken() return ( <SessionProvider> {children} </SessionProvider> ) }; I know it's not good idea to just call the function like that but It's for testing anyways. And now I have my other client component for calling SpotifyAPI with previous set access_token but it's not there anymore. tsx //Component.tsx "use client"; import { SpotifyAPIClient } from '@/database/spotify'; import React, { useEffect, useState } from 'react' import TopListBox from './TopListBox'; const TopStatsContainer = () => { const [myTopArtists, myTopArtistsSet] = useState([]); async function fetchOverviewStats(){ const topArtists = SpotifyAPIClient.getMyTopArtists(3); myTopArtistsSet((await topArtists).data.items) } useEffect(() => { console.log(' - TopStatsContainer (useEffect): ', SpotifyAPIClient.getAccessToken()) fetchOverviewStats() }, []) return ( <div className='top__stats_container'> <h1>My overwiew</h1> <TopListBox title={"test"} data={myTopArtists} /> </div> ) } and of course while calling `.getMyTopArtists()` function I get the error because access_token is not set in this instance but I don't know why.
this is not gonna work
the client will create a new instance of the class
Chilean jack mackerelOP
well now I know that, but I'm looking for an idea to make it work
use server action
// action.ts
'use server'

export async function getSpotifyAccessToken() {
  return SpotifyAPIClient.getAccessToken()
}

export async function setSpotifyAccessToken(token) {
  SpotifyAPIClient.setAccessToken(token)
}
Chilean jack mackerelOP
well okaay, but can I use my already created SpotifyAPI class with other functions or I have to convert it into server actions?
where you gonna host it?
it is not gonna work too if you host it on some serverless platform
Chilean jack mackerelOP
ugh i thought about cloudflare
but you know that this access_token is not global for entire website but specific for every user that is trying to access API right?
why dont you store it on db?
@Ray why dont you store it on db?
Chilean jack mackerelOP
well mostly because then I would have to check for the access_token in database everytime and refresh it if it's expired and the whole process would be longer
you could use redis for it
Chilean jack mackerelOP
So it's not possible to set the access_token once for entire page like in context and get this token from everywhere in the project right?
but you will lose the data if it restart
Chilean jack mackerelOP
I just want to remember the access_token while page is being used, on the any other visit it's gonna set the access_token again anyway.
what is the access_token for?
maybe cookie is a good place for it
Chilean jack mackerelOP
for being able to call requests for Spotify API
@Ray maybe cookie is a good place for it
Chilean jack mackerelOP
but do I have the access for cookies inside server sided code?
Chilean jack mackerelOP
well, I will try it then, thanks for the idea
Chilean jack mackerelOP
oh well I just realised that I if modify the SpotifyAPI class to get and set the access_token based on cookies using next/headers then I can't use it inside client components :/
pass it down from server component
and use server action to set on client side
Chilean jack mackerelOP
well, then I have to give up with idea of structure I already created in spotify.ts
Chilean jack mackerelOP
I have to pass cookies with access_token for every client component and use it with api calls
@Ray ts // action.ts 'use server' export async function getSpotifyAccessToken() { return SpotifyAPIClient.getAccessToken() } export async function setSpotifyAccessToken(token) { SpotifyAPIClient.setAccessToken(token) }
Chilean jack mackerelOP
okay I've done it using server actions, but I have one strange bug. When I'm logging out the access_token is still present so requests are going through and only after I refresh page the access_token is being nulled
did you clear it on cookie when you log out?
Chilean jack mackerelOP
//action.ts
'use server'

import { getCurrentServerSession } from "@/database/auth"
import { SpotifyAPIClient } from "@/database/spotify"

export async function getSpotifyAccessToken() {
    return SpotifyAPIClient.getAccessToken()
}

export async function setSpotifyAccessToken() {
    const b = await getCurrentServerSession()
    if(b)
            SpotifyAPIClient.setAccessToken(b.access_token)
    else
        SpotifyAPIClient.setAccessToken('')
    
}

and now instead of getting access_token by SpotifyAPIClient.getAccessToken() inside API request function I just call the const access_token = await getSpotifyAccessToken() and it works fine but only with this bug
Answer
Chilean jack mackerelOP
well i'm not doing anything with cookies right now
only server actions
<button onClick={() =>{ signOut()}}>logout</button>
just using NextAuth function
I think you need to call setSpotifyAccessToken when you do signout
Chilean jack mackerelOP
maybe i just need to set the access_token manually to null after logging out
yeah
yeah it's working just fine now. Thank you very much for help!
I suggest you deploy it to cloudflare and see if it actually work before you continue developing it
I'm not sure is this going to work in any serverless platform
Chilean jack mackerelOP
well i deployed it on vercel, because nextauth has some problems while deploying on cfpages and the result that it's working but with other bugs
on the first login the access_token is not being set but after refresh it's doing fine, and after logout, the bug from above is back