twilio-client package isn't working with next.js

Hari
Hari
edited June 14 in Voice

Hi, I'm trying to build an app that involves app to app calling using the Twilio API. My project is built using Next.js, and the twilio-client package isn't running properly.

Whenever I import the package, I get one of two errors:

TypeError: Cannot redefine property: instance

or

ReferenceError: window is not defined

To reproduce this issue,

  • Create a new Next.js application using npx create-next-app
  • Install twilio-client using npm install twilio-client --save
  • Create a page and import twilio-client, using either the CommonJS or ES6 syntax.
  • Visit the page.

Does anyone have any experience with this? Can somebody help me out?

Tagged:

Best Answer

  • bdelvalle
    bdelvalle
    edited June 21 Accepted Answer

    Hi @Hari and @sridarm

    I was able to get that walkie-talkie tutorial working in next.js with the following component:

    import { useEffect,useState } from 'react';

    export default function VoiceRoom(){
    const [device, setDevice] = useState(null)
    const [identity, setIdentity] = useState("")
    const [status, setStatus] = useState(null)
    const [ready, setReady] = useState(false)

    const setup = async (event) => {
        event.preventDefault();
    
        try {
            const response = await fetch(`https://YOUR-WALKIE-TALKIE-URL-dev.twil.io/token?identity=${identity}`)
            const data = await response.json();
            device.setup(data.accessToken);
            device.audio.incoming(false);
            device.audio.incoming(false);
            device.audio.outgoing(false);
            device.audio.disconnect(false);
            setDevice(device)
        } catch (err) {
            console.log(err)
        }
    }
    
    const connect = () => {
        const recipient = identity === 'friend1' ? 'friend2' : 'friend1';
        device.connect({recipient})
    }
    
    const disconnect = () => {
        device.disconnectAll();
    }
    
    useEffect(()=>{
    
        async  function  createDevice(){
    
            const Device = (await  import('twilio-client')).Device
            const device = new Device();
                         
            device.on('incoming', connection  => {
                //Whatever logic you need
                connection.accept()
    
                setStatus(connection.status())
            });
    
            device.on('ready', () => {
                setStatus("device ready");
                setReady(true);
            })
            
            device.on('connect', connection => {
                setStatus(connection.status())
            })
    
            device.on('disconnect', connection => {
                setStatus(connection.status())
            })
    
            setDevice(device);
        }
        
        createDevice()
            
        }, [])
    
    
    return(
        <div>
            <p>Hello</p>
            { ready 
            ? <button
                onMouseDown={connect}
                onMouseUp={disconnect}
                >Press to Talk</button>
            : <div>
                <p>Enter your name to begin.</p>
                    <form onSubmit={(e) => setup(e)}>
                        <input
                            value={identity}
                            onChange={(e) => setIdentity(e.target.value)}
                            type="text"
                            placeholder="What's your name?"></input>
                        <input type="submit" value="Begin Session"></input>    
                    </form>    
                </div>  
            }
            <p>{status}</p>
        </div>
    )
    

    }

    I'm not super well-versed with hooks (yet), so apologies if it's not the most beautiful Next app :smile: (I'm not totally sure when I should call setDevice to update the device in state, but this is the best I could do in the time I have.)

    In the code snippet above, it looked like createDevice() was called outside the useEffect hook. Also, I used the useState hook to keep track of the device object within the component.

Answers

  • Hi Hari.

    The window being undefined made me think it's an issue with the code executing on the server side, rather than client-side only.

    There seems to be a variety of workarounds to this, and I was able to load a page without errors using this option:

    if (typeof window !== 'undefined') {
      const Device = require('twilio-client')
    }
    

    and this option:

    import dynamic from 'next/dynamic';
    const Device = dynamic(import('twilio-client'), { ssr: false });
    

    Hopefully that helps. If not, just post here and we can try something else!

  • Hari
    Hari
    edited June 16

    Hi, thank you for the quick response! Both of your solutions are allowing me to load the page, but I can't seem to initialize the device object properly.

    I've been following this tutorial, https://twilio.com/blog/build-a-browser-based-walkie-talkie-react-twilio-programmable-voice-functions, but using Next.js instead of create-react-app, and using functional components instead of class components.

    After importing twilio-client, I tried initializing it inside the useEffect hook, like so:

    import { useEffect } from  'react';
    import  dynamic  from  'next/dynamic';
    
    const  Device = dynamic(import('twilio-client'), { ssr:  false });
    
    export  default  function  VoiceRoom(){
    
        //useEffect doesn't run on the server-side
        useEffect(()=>{
            const device = new  Device();
        }, [])
    
        return(
            <div>
                <p>Hello</p>
            </div>
        )
    }
    

    The webpage shows the error TypeError: Device is not a constructor

    console.log(typeof(Device)) shows that it's apparently an object. Am I doing something wrong?

  • bdelvalle
    bdelvalle
    edited June 16

    Hm, it looks like I should have put this in the examples above:

    const { Device } = dynamic(import('twilio-client'), { ssr: false });
    

    to pull out just the Device property from the twilio-client package using ES6 destructuring.

    Hopefully that works!

  • Any solutions available for this issue ?

    I tries @bdelvalle suggestions, but no luck.

    Any other suggestions or workaround ?

    Thanks in advance

  • I haven't had time to fully finish and test out what I was working on, but for now the following code doesn't produce issues.

    import { useEffect } from  'react';
    
    
    export  default  function  VoiceRoom(){
    
        //useEffect doesn't run on the server-side
        useEffect(()=>{
    
            //for await to work inside useEffect, wrap it in an async function
            async  function  createDevice(){
    
                const  Device = (await  import('twilio-client')).Device
                const  device = new  Device();
    
                //For some reason returning the device and then using it doesn't work
                //So keep all the "device.on()" stuff inside the async function
    
                device.on('incoming', connection  => {
                    //Whatever logic you need
                });
    
            }, [])
    
    
            createDevice()
    
        return(
            <div>
                <p>Hello</p>
            </div>
        )
    }
    

    Note that I haven't been able to test it out yet, but I don't get any errors when visiting the webpage.

  • @Hari thanks for the workaround, this seems working, although there is some other issue which is not related to this, I guess.

  • Thank you so much for your help!