I am trying to load a TradingView chart using React by making use of the following:
useScript.js
export type Status = 'idle' | 'loading' | 'ready' | 'error'
export type ScriptElt = HTMLScriptElement | null
function useScript(src: string): Status {
const [status, setStatus] = useState<Status>(src ? 'loading' : 'idle')
useEffect(
() => {
if (!src) {
setStatus('idle')
return
}
// Fetch existing script element by src
// It may have been added by another instance of this hook
let script: ScriptElt = document.querySelector(`script[src="${src}"]`)
if (!script) {
// Create script
script = document.createElement('script')
script.src = src
script.async = true
script.setAttribute('data-status', 'loading')
// Add script to document body
document.body.appendChild(script)
// Store status in attribute on script
// This can be read by other instances of this hook
const setAttributeFromEvent = (event: Event) => {
script?.setAttribute('data-status', event.type === 'load' ? 'ready' : 'error')
}
script.addEventListener('load', setAttributeFromEvent)
script.addEventListener('error', setAttributeFromEvent)
} else {
// Grab existing script status from attribute and set to state.
setStatus(script.getAttribute('data-status') as Status)
}
// Script event handler to update status in state
// Note: Even if the script already exists we still need to add
// event handlers to update the state for *this* hook instance.
const setStateFromEvent = (event: Event) => {
setStatus(event.type === 'load' ? 'ready' : 'error')
}
// Add event listeners
script.addEventListener('load', setStateFromEvent)
script.addEventListener('error', setStateFromEvent)
// Remove event listeners on cleanup
// eslint-disable-next-line consistent-return
return () => {
if (script) {
script.removeEventListener('load', setStateFromEvent)
script.removeEventListener('error', setStateFromEvent)
}
}
},
[src], // Only re-run effect if script src changes
)
return status
}
export default useScript
The TradingView component that I'm attempting to load:
TradingView.tsx
import { useTranslation } from 'contexts/Localization'
import useScript from 'hooks/useScript'
import React, { useEffect, useRef } from 'react'
import { DefaultTheme, useTheme } from 'styled-components'
/**
* When the script tag is injected the TradingView object is not immediately
* available on the window. So we listen for when it gets set
*/
const tradingViewListener = async () =>
new Promise<void>((resolve) =>
Object.defineProperty(window, 'TradingView', {
configurable: true,
set(value) {
this.tv = value
resolve(value)
},
}),
)
const initializeTradingView = (TradingViewObj: any, theme: DefaultTheme, localeCode: string, opts: any) => {
let timezone = 'Etc/UTC'
try {
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
} catch (e) {
// noop
}
/* eslint-disable new-cap */
/* eslint-disable no-new */
// @ts-ignore
return new TradingViewObj.widget({
// Advanced Chart Widget uses the legacy embedding scheme,
// an id property should be specified in the settings object
id: opts.container_id,
autosize: true,
height: '100%',
symbol: 'BINANCE:BNBUSDT',
interval: '5',
timezone,
theme: theme.isDark ? 'dark' : 'light',
style: '1',
locale: localeCode,
toolbar_bg: '#f1f3f6',
enable_publishing: false,
allow_symbol_change: true,
hide_side_toolbar: false,
enabled_features: ['header_fullscreen_button'],
...opts,
})
}
interface TradingViewProps {
id: string
symbol: string
}
const TradingView = ({ id, symbol }: TradingViewProps) => {
const { currentLanguage } = useTranslation()
const theme = useTheme()
const widgetRef = useRef<any>()
const { isMobile } = useMatchBreakpoints()
useScript('https://s3.tradingview.com/tv.js')
useEffect(() => {
const opts: any = {
container_id: id,
symbol,
}
if (isMobile) {
opts.hide_side_toolbar = true
}
// @ts-ignore
if (window.tv) {
// @ts-ignore
widgetRef.current = initializeTradingView(window.tv, theme, currentLanguage.code, opts)
} else {
tradingViewListener().then((tv) => {
widgetRef.current = initializeTradingView(tv, theme, currentLanguage.code, opts)
})
}
// Ignore isMobile to avoid re-render TV
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [theme, currentLanguage, id, symbol])
return (
<Box overflow="hidden" className="tradingview_container">
<div id={id} />
</Box>
)
}
export function useTradingViewEvent({
id,
onNoDataEvent,
onLoadedEvent,
}: {
id?: string
onNoDataEvent?: () => void
onLoadedEvent?: () => void
}) {
useEffect(() => {
const onNoDataAvailable = (event: MessageEvent) => {
const payload = event.data
if (payload.name === 'tv-widget-no-data') {
if (id && payload.frameElementId === id) {
onNoDataEvent?.()
}
}
if (payload.name === 'tv-widget-load') {
if (id && payload.frameElementId === id) {
onLoadedEvent?.()
}
}
}
window.addEventListener('message', onNoDataAvailable)
// eslint-disable-next-line consistent-return
return () => {
window.removeEventListener('message', onNoDataAvailable)
}
}, [id, onNoDataEvent, onLoadedEvent])
}
// Required to link to TradingView website for the widget
export const TradingViewLabel = ({ symbol, ...props }: { symbol: string } & FlexProps) => {
const { t } = useTranslation()
return (
<Flex alignItems="center" px="24px" {...props}>
<Link fontSize="14px" href={`https://www.tradingview.com/symbols/${symbol}`} external>
BNB {t('Chart')}
</Link>
<Text fontSize="14px" ml="4px">
{t('by')} TradingView
</Text>
</Flex>
)
}
export default TradingView
I have also attempted to load TradingView using react-helmet-async by making use of:
<Helmet>
<script src="https://s3.tradingview.com/tv.js" type="text/javascript" id="tradingViewWidget" />
</Helmet>
Which also didn't render.
Does anyone have any idea why this component might not be rendering?
I have attempted to render the Tradingview chart without making use of existing UI components which also isn't working.
I am also getting the following error upon inspection of Firefox's console:
Content Security Policy: The page’s settings blocked the loading of a resource at inline (“script-src”).
Could this be causing the chart to not render?
Call made to the TradingView component in an attempt to render a chart:
import TradingViewChart from 'components/TradingView'
import React from 'react'
const TRADING_VIEW_COMPONENT_ID = 'tradingview_b239c'
const TradingView = () => {
return <TradingViewChart id={TRADING_VIEW_COMPONENT_ID} symbol="BINANCE:BNBUSDT" />
}
export default TradingView