React中如何让useEffect在第三方Drift库加载后触发?
window.drift Becomes Defined Problem
I'm trying to create a <Drift> component to mount the Drift chat service. Here's my code:
import React, { useEffect, useState } from 'react' export default function Drift (props) { const [drift, setDrift] = useState(undefined) // 执行一次drift脚本 useEffect(() => { const script = document.getElementById('drift-script').innerHTML window.eval(script) }, []) // 轮询检查drift是否可用 useEffect(() => { const poller = setInterval(() => { if (window.drift) { setDrift(window.drift) clearInterval(poller) console.log('setDrift(fn) called') } }, 500) return () => clearInterval(poller) }, []) // 配置drift组件 useEffect(() => { console.log('drift depended effect triggered:') console.log(typeof drift) if (drift) { console.log('drift is defined') drift.config({ messages: { welcomeMessage: 'Your code is being generated!' } }) drift.on('ready', function (api) { console.log('drift ready') api.widget.hide() api.widget.show() api.showWelcomeMessage() }) } else { console.log('drift is undefined') } }, [drift]) return ( <div> <script id="drift-script" dangerouslySetInnerHTML={{ __html: ` "use strict"; !function() { var t = window.driftt = window.drift = window.driftt || []; if (!t.init) { if (t.invoked) return void (window.console && console.error && console.error("Drift snippet included twice.")); t.invoked = !0, t.methods = [ "identify", "config", "track", "reset", "debug", "show", "ping", "page", "hide", "off", "on" ], t.factory = function(e) { return function() { var n = Array.prototype.slice.call(arguments); return n.unshift(e), t.push(n), t; }; }, t.methods.forEach(function(e) { t[e] = t.factory(e); }), t.load = function(t) { var e = 3e5, n = Math.ceil(new Date() / e) * e, o = document.createElement("script"); o.type = "text/javascript", o.async = !0, o.crossorigin = "anonymous", o.src = "https://js.driftt.com/include/" + n + "/" + t + ".js"; var i = document.getElementsByTagName("script")[0]; i.parentNode.insertBefore(o, i); }; } }(); drift.SNIPPET_VERSION = '0.3.1'; drift.load('DRIFT_ID'); ` }} /> { props.children } </div> ) }
My expected behavior is that the useEffect dependent on drift should trigger once window.drift is fully defined. However, in practice, this effect only runs once when window.drift is undefined, with the following logs:
drift depended effect triggered: undefined drift is undefined setDrift(fn) called
How can I properly reference window.drift so that the useEffect triggers once it's defined?
Solution
The core issue here is that the initial window.drift value set by the snippet is an array (a command queue), not a fully initialized SDK object—your poller picks up this array early, sets it to state, but when the actual Drift SDK loads later, it replaces the array with the full API object. Since your state was already set to the array, React doesn't re-run the useEffect because the state reference didn't update after that.
Here are a few ways to fix this:
1. Adjust the Poller to Check for a Fully Initialized Drift Object
Instead of checking just window.drift, verify that it has a method that only exists on the fully initialized SDK (like config being a real function, not a factory wrapper):
useEffect(() => { const poller = setInterval(() => { // Check if drift exists AND has a proper config method if (window.drift && typeof window.drift.config === 'function') { setDrift(window.drift) clearInterval(poller) console.log('setDrift(fn) called') } }, 500) return () => clearInterval(poller) }, [])
2. Clean Up the Script Loading (Avoid eval)
Embedding the script via dangerouslySetInnerHTML and eval-ing it is unnecessary and carries security risks. Instead, create the script element dynamically:
useEffect(() => { const script = document.createElement('script') script.type = 'text/javascript' script.async = true script.crossOrigin = 'anonymous' script.textContent = ` "use strict"; !function() { var t = window.driftt = window.drift = window.driftt || []; if (!t.init) { if (t.invoked) return void (window.console && console.error && console.error("Drift snippet included twice.")); t.invoked = !0, t.methods = [ "identify", "config", "track", "reset", "debug", "show", "ping", "page", "hide", "off", "on" ], t.factory = function(e) { return function() { var n = Array.prototype.slice.call(arguments); return n.unshift(e), t.push(n), t; }; }, t.methods.forEach(function(e) { t[e] = t.factory(e); }), t.load = function(t) { var e = 3e5, n = Math.ceil(new Date() / e) * e, o = document.createElement("script"); o.type = "text/javascript", o.async = !0, o.crossorigin = "anonymous", o.src = "https://js.driftt.com/include/" + n + "/" + t + ".js"; var i = document.getElementsByTagName("script")[0]; i.parentNode.insertBefore(o, i); }; } }(); drift.SNIPPET_VERSION = '0.3.1'; drift.load('DRIFT_ID'); ` document.body.appendChild(script) return () => document.body.removeChild(script) }, [])
3. Best Option: Use Drift's Built-in Command Queue
Drift's snippet is designed to handle commands before the SDK is fully loaded—you can push your config and ready handler directly into the initial window.drift array, which will execute automatically once the SDK loads. This eliminates polling entirely:
import React, { useEffect } from 'react' export default function Drift (props) { useEffect(() => { // Initialize Drift snippet const script = document.createElement('script') script.type = 'text/javascript' script.async = true script.crossOrigin = 'anonymous' const timestamp = Math.ceil(new Date() / 300000) * 300000 script.src = `https://js.driftt.com/include/${timestamp}/DRIFT_ID.js` document.body.appendChild(script) // Configure Drift (commands are queued and run when ready) window.drift = window.drift || [] window.drift.push(['config', { messages: { welcomeMessage: 'Your code is being generated!' } }]) window.drift.push(['on', 'ready', function(api) { console.log('drift ready') api.widget.hide() api.widget.show() api.showWelcomeMessage() }]) return () => { document.body.removeChild(script) // Optional cleanup window.drift = undefined } }, []) return <div>{ props.children }</div> }
Why Your Original Code Failed
The initial window.drift created by the snippet is an array with factory methods that push commands to a queue. Your poller detected this array and set it to state, but when the actual SDK loaded, it replaced window.drift with the full API object. Since your state was already set to the array, React didn't re-run the useEffect because the state reference didn't change after that. By checking for a real SDK method (like config being a function), you ensure you only set state once the SDK is fully ready.
内容的提问来源于stack exchange,提问作者Tom

