moonstar-x / react-twitch-embed

A collection of components to embed Twitch.
https://docs.moonstar-x.dev/react-twitch-embed
MIT License
24 stars 7 forks source link

[SSR] TwitchChat and TwitchClip no window reference when components are rendered in the first page of app #29

Closed moonstar-x closed 2 years ago

moonstar-x commented 3 years ago

The function that adds the website domain into the parent parameters of the clip and chat urls requires access to the window object which is not defined on SSR apps at the stage it's been used. This only happens when these components are loaded on the first page accessed of the app.

ghost commented 3 years ago

This might help. It's how I implemented my embed (mimicking the devs that are writing the project, I'm on a learning path and implementing features my way):

import useScript, { ScriptStatus } from '@charlietango/use-script'

const Embed = ({
    id = 'twitch-embed',
    allowFullscreen = true,
    fontSize = 'small',
    height = 480,
    withChat = false,
    theme = 'dark',
    width = 940,
    onAuthenticate = () => null,
    onVideoPlay = () => null,
    onVideoPause = () => null,
    onVideoReady = () => null,
    autoplay = true,
    muted = false,
    parent = []
}) => {
    const [ready, status] = useScript('https://embed.twitch.tv/embed/v1.js');

    useEffect(() => {
        if(window.Twitch !== undefined && status !== ScriptStatus.ERROR) {
            if(player === false) {
                const newPlayer = new window.Twitch.Embed(id, {
                    allowfullscreen: allowFullscreen,
                    channel: viewing,
                    'font-size': fontSize,
                    height: '100%',
                    layout: withChat ? 'video-with-chat' : 'video',
                    theme: theme,
                    width: '100%',
                    parent: parent
                });

                newPlayer.play();
                newPlayer.setMuted(false);
        }
    }, [ready, status]);

    return(
        <></>
    )
}

export default Embed;

The goal here is to, at a component-level, wait for the importation of an outside script.

moonstar-x commented 3 years ago

This might help. It's how I implemented my embed (mimicking the devs that are writing the project, I'm on a learning path and implementing features my way):


import useScript, { ScriptStatus } from '@charlietango/use-script'

const Embed = ({

    id = 'twitch-embed',

    allowFullscreen = true,

    fontSize = 'small',

    height = 480,

    withChat = false,

    theme = 'dark',

    width = 940,

    onAuthenticate = () => null,

    onVideoPlay = () => null,

    onVideoPause = () => null,

    onVideoReady = () => null,

    autoplay = true,

    muted = false,

    parent = []

}) => {

    const [ready, status] = useScript('https://embed.twitch.tv/embed/v1.js');

    useEffect(() => {

        if(window.Twitch !== undefined && status !== ScriptStatus.ERROR) {

            if(player === false) {

                const newPlayer = new window.Twitch.Embed(id, {

                    allowfullscreen: allowFullscreen,

                    channel: viewing,

                    'font-size': fontSize,

                    height: '100%',

                    layout: withChat ? 'video-with-chat' : 'video',

                    theme: theme,

                    width: '100%',

                    parent: parent

                });

                newPlayer.play();

                newPlayer.setMuted(false);

        }

    }, [ready, status]);

    return(

        <></>

    )

}

export default Embed;

The goal here is to, at a component-level, wait for the importation of an outside script.

Thanks for your input. This is more a solution for downloading the embed scripts from Twitch. (It's still pretty helpful, I'm gonna check it out for later). However this issue is more related to the chat and clip components that are just a simple iframe. Though, since the iframe URL requires to specify the domain name that is using the frame, accessing it through the window object seems less straightforward than thought, at least for SSR apps.

ghost commented 3 years ago

@moonstar-x In Next.js, the window object is not available server-side. For Next.js, dynamic imports (or useEffect) are the intended/explicit way to go. It's not a hack, it's how things are done in order to differentiate between client-side and server-side code.

In fact, I remember reading explictly about this on the Next.js discussion boards and a dev explicitly saying to use dynamic imports for outside scripts that need to access the window object, see:

const TwitchAPI = dynamic(() => import("https://www.twitch/stuff.js"), { ssr: false });

^ This ensures that, although your page is SSR'd, it will load that script on the client-side.

moonstar-x commented 3 years ago

I know the window object is not defined globally in SSR. However, it is defined inside the lifecycle methods of class based components. (Which is the same thing as useEffect when using hooks). I'm gonna dig a bit more on this. It's a bit complicated to debug though because my next.js page that I use to see if components get rendered will throw this error sometimes and sometimes not. :(

moonstar-x commented 2 years ago

Hi there, sorry for the long hiatus.

I'm gonna close this issue due to inactivity. However, I have just updated this package to version 3.0.1 with #37.

Any reference to window or document have been moved to useEffect() which should be executed in the client for SSR apps.

If you feel this was a mistake, feel free to re-open this issue.

Thanks!