1

I have playerInstance variable on which I can call methods like togglePlay(). I wanna be able to call it's methods from different components. I tried to do this with Context API but it only works for sharing properties, when I'm trying to call spotifyPlayer.togglePlay() it shows an error: spotifyPlayer.togglePlay is not a function. I guess that useState() only stores properties but then how am I supposed to share methods? Is even using Context API any good for this problem? I already use redux-toolkit for sharing data

SpotifyPlayerContext.jsx

import React, { createContext, useEffect, useState } from "react"
import { useSelector } from "react-redux"

export const SpotifyPlayerContext = createContext()

export const SpotifyPlayerProvider = ({ children }) => {
    const user = useSelector((state) => state.user.value)
    const [spotifyPlayer, setSpotifyPlayer] = useState()

    useEffect(() => {
        console.log(spotifyPlayer)
    }, [spotifyPlayer])

    const initSpotifyPlayer = () => {
        const script = document.createElement("script")
        script.src = "https://sdk.scdn.co/spotify-player.js"
        script.async = true
        document.body.appendChild(script)

        window.onSpotifyWebPlaybackSDKReady = () => {
            const playerInstance = new window.Spotify.Player({
                name: "Figure Out The Lyrics",
                getOAuthToken: (cb) => {
                    cb(user.token)
                },
            })

            playerInstance.connect()

            playerInstance.addListener("ready", ({ device_id }) => {
                console.log("Device ID", device_id)
                playerInstance.device_id = device_id
                setSpotifyPlayer(playerInstance)
            })
            playerInstance.addListener("player_state_changed", (state) => {
                console.log("player state changed", state)
                playerInstance.playback = state
                setSpotifyPlayer({ ...playerInstance })
                console.log("instance", playerInstance)
            })
        }
    }

    const value = { spotifyPlayer, setSpotifyPlayer, initSpotifyPlayer }

    return <SpotifyPlayerContext.Provider value={value}> {children} </SpotifyPlayerContext.Provider>
}

export default SpotifyPlayerContext

Player.jsx

import React, { useContext, useEffect, useState } from "react"
import { useDispatch, useSelector } from "react-redux"
import { Tooltip } from "react-tooltip"
import SpotifyPlayerContext from "../SpotifyPlayerContext"

const Player = ({ spotifyAPI }) => {
    const { spotifyPlayer, setSpotifyPlayer } = useContext(SpotifyPlayerContext)

    useEffect(() => {
        if (!spotifyPlayer) return
        if (!spotifyPlayer.playback) spotifyAPI.transferMyPlayback([spotifyPlayer.device_id])
        console.log("useEffect in Player", spotifyPlayer)
    }, [spotifyPlayer])

    return spotifyPlayer?.playback ? (
        <button onClick={() => {console.log("spotifyPlayer", spotifyPlayer);spotifyPlayer.togglePlay()}}>toggle</button>
    ) : (
        <div>loading</div>
    )
}

export default Player
szym
  • 21
  • 5
  • Where are you rendering `SpotifyPlayerProvider` in the tree? – Unmitigated Jun 17 '23 at 17:30
  • In main.jsx ` ` – szym Jun 17 '23 at 17:32
  • Are you calling initSpotifyPlayer anywhere? The code looks good. This is exactly what the context is for. Redux state needs to be serializable, so you can't use redux to share this object – yxre Jun 17 '23 at 17:34
  • I call it, the properties are available but not methods. It looks like I can share properties but not methods – szym Jun 17 '23 at 17:36

2 Answers2

0

Why don't you try creating method to call togglePlay in Provider and use it in your component? Doesn't it work?

  • This function in Provider `const togglePlay = () => spotifyPlayer.toggle()` still shows the same error. Seems like I only can call playerInstance.togglePlay(), when i try spotifyPlayer.toggle() it doesn't work. – szym Jun 17 '23 at 19:29
  • I see you passed initSpotifyPlayer function to your component(Player.jsx) but you didnt call it in your component. Shouldnt you call it first? – semihozker Jun 17 '23 at 20:20
  • It's called in different component, so the spotifyPlayer is initialized. I'm even able to use it's properties. – szym Jun 17 '23 at 20:21
  • You using method of instance may not possible but here is a topic similar yours https://stackoverflow.com/questions/62262385/react-context-not-updating-for-class-as-value – semihozker Jun 17 '23 at 22:43
0

Okay, I was able to make it work. Instead of creating playerInstance when the onSpotifyWebPlaybackSDKReady I created it even outside of the component and only assign the player to it. So now I get data from spotifyPlayer but I call methods on playerInstance. Maybe it's not perfect but it looks like it works for now.

Updated SpotifyPlayerContext.jsx

import React, { createContext, useEffect, useState } from "react"
import { useSelector } from "react-redux"

export const SpotifyPlayerContext = createContext()

let playerInstance; // create variable

export const SpotifyPlayerProvider = ({ children }) => {
    const user = useSelector(state => state.user.value)
    const [spotifyPlayer, setSpotifyPlayer] = useState()

    useEffect(() => {
        console.log(spotifyPlayer)
    }, [spotifyPlayer])

    const initSpotifyPlayer = () => {
        const script = document.createElement("script")
        script.src = "https://sdk.scdn.co/spotify-player.js"
        script.async = true
        document.body.appendChild(script)

        window.onSpotifyWebPlaybackSDKReady = () => { // only assign
            playerInstance = new window.Spotify.Player({
                name: "Figure Out The Lyrics",
                getOAuthToken: (cb) => {
                    cb(user.token)
                },
                volume: 0.1
            })

            playerInstance.connect()

            playerInstance.addListener("ready", ({ device_id }) => {
                console.log("Device ID", device_id)
                playerInstance.device_id = device_id
                setSpotifyPlayer(playerInstance)
            })
            playerInstance.addListener("player_state_changed", (state) => {
                console.log("player state changed", state)
                playerInstance.playback = state
                setSpotifyPlayer({ ...playerInstance })
                console.log("instance", playerInstance)
            })
        }
    }

    const value = { spotifyPlayer, setSpotifyPlayer, initSpotifyPlayer, playerInstance } // export playerInstance

    return <SpotifyPlayerContext.Provider value={value}> {children} </SpotifyPlayerContext.Provider>
}

export default SpotifyPlayerContext

Updated Player.jsx

import React, { useContext, useEffect, useState } from "react"
import { useDispatch, useSelector } from "react-redux"
import { Tooltip } from "react-tooltip"
import SpotifyPlayerContext from "../SpotifyPlayerContext"

const Player = ({ spotifyAPI }) => {
    const { spotifyPlayer, setSpotifyPlayer, playerInstance } = useContext(SpotifyPlayerContext) // import playerInstance

    useEffect(() => {
        if (!spotifyPlayer) return
        if (!spotifyPlayer.playback) spotifyAPI.transferMyPlayback([spotifyPlayer.device_id])
        console.log("useEffect in Player", spotifyPlayer)
    }, [spotifyPlayer])

    return spotifyPlayer?.playback && playerInstance ? (
        <button onClick={() => playerInstance.togglePlay()}>toggle</button> // call it's methods
    ) : (
        <div>loading</div>
    )
}

export default Player
szym
  • 21
  • 5