I am trying to learn React by creating a small and simple web app with Next.js, but I am seeing some very strange bugs.
The app uses a node package to display charts from data that comes from a database. It sets a javascript interval to refresh its data. I could write this app without React in 20 minutes, but trying to use React to develop it has taken a week and it still isn’t working. I’ll explain the problems below.
Here are my 3 source files. (To save space, I removed the SQL queries. They are well-tested and are not causing these problems.)
page.tsx
import Chart from './Chart.tsx';
const ServerComponent = () => {
return (
<div>
<Chart divID="xau-usd-candlestick" refreshMinutes="5" chartType="candlestick" currencyFrom="xau" currencyTo="usd" decimalPlaces="2" />
<Chart divID="cny-usd-candlestick" refreshMinutes="5" chartType="candlestick" currencyFrom="cny" currencyTo="usd" decimalPlaces="4" />
<Chart divID="try-usd-candlestick" refreshMinutes="5" chartType="candlestick" currencyFrom="try" currencyTo="usd" decimalPlaces="6" />
<Chart divID="irr-usd-candlestick" refreshMinutes="5" chartType="candlestick" currencyFrom="irr" currencyTo="usd" decimalPlaces="8" />
<Chart divID="btc-usd-candlestick" refreshMinutes="5" chartType="candlestick" currencyFrom="btc" currencyTo="usd" decimalPlaces="2" />
</div>
);
};
export default ServerComponent;
Chart.tsx
'use client'
import styles from './page.module.css';
import { useState, useEffect } from 'react';
import Script from 'next/script'
async function renderChart (divID, chartType, currencyFrom, currencyTo, decimalPlaces)
{
let url = 'http://localhost:3000/api?chart-type=' + chartType;
url += '&cur-from=' + currencyFrom + '&cur-to=' + currencyTo + '&decimal-places=' + decimalPlaces;
await fetch (url)
.then ((response) => { return response.text (); })
.then ((data) => { Highcharts.stockChart (divID, JSON.parse (data).data); })
.catch ((error) => { console.log (error, currencyFrom); });
}
function Chart (props)
{
if (typeof props.refreshMinutes != 'undefined' && props.refreshMinutes > 0)
{
useEffect (() => {
const interval = setInterval (renderChart, props.refreshMinutes * 60000, props.divID, props.chartType, props.currencyFrom, props.currencyTo, props.decimalPlaces);
return () => { clearInterval (interval); };
}, []);
}
let css = `${styles.chart}`;
return (
<div className={styles.main}>
<div id={props.divID} className={css}>
</div>
<Script
src = "https://code.highcharts.com/stock/highstock.js"
onReady = { () => { renderChart (props.divID, props.chartType, props.currencyFrom, props.currencyTo, props.decimalPlaces); }}
/>
</div>
);
}
export default Chart;
api/route.ts
const { Client } = require ('pg');
const pg_client =
{
host: '127.0.0.1',
port: 5432,
database: 'db_name',
user: 'xxxxx',
password: 'xxxxx'
};
const db_client = new Client (pg_client);
async function connect () {
await db_client.connect ();
}
await connect ();
async function getCandlestickData (cur_from, cur_to, decimal_places)
{
return new Promise (async (resolve, reject) =>
{
var query = 'select .....';
await db_client.query (query)
.then ((db_result) =>
{
for (let i = 0; i < db_result.rows.length; i++)
{
db_result.rows [i].x = Number (db_result.rows [i].x) * 1000;
db_result.rows [i].open = Number (Number (db_result.rows [i].open).toFixed (decimal_places));
db_result.rows [i].close = Number (Number (db_result.rows [i].close).toFixed (decimal_places));
db_result.rows [i].low = Number (Number (db_result.rows [i].low).toFixed (decimal_places));
db_result.rows [i].high = Number (Number (db_result.rows [i].high).toFixed (decimal_places));
}
let result =
{
plotOptions: {
candlestick: {
color: 'pink',
lineColor: 'red',
upColor: 'lightgreen',
upLineColor: 'green'
}
},
rangeSelector: { selected: 1 },
title: { text: cur_from + '-' + cur_to },
series: [
{
type: 'candlestick',
name: cur_from + '-' + cur_to,
data: db_result.rows
}
]
};
resolve (result);
})
.catch ((error) => { reject (error); });
});
}
export async function GET (request: Request) {
let data;
let url = new URL (request.url);
let chartType = url.searchParams.get ('chart-type');
if (chartType == 'candlestick')
{
let currencyFrom = url.searchParams.get ('cur-from').toUpperCase ();
let currencyTo = url.searchParams.get ('cur-to').toUpperCase ();
let decimalPlaces = url.searchParams.get ('decimal-places').toUpperCase ();
await getCandlestickData (currencyFrom, currencyTo, decimalPlaces).then ((result) => { data = result; });
}
return Response.json({ data });
}
There are several problems I’m seeing, but here are the main two:
The weirdest thing I am noticing is that the second chart in the list does not display. If I rearrange the <Chart>
elements in page.tsx
, it is still the second one that doesn’t display. It seems like the onReady
function for the <Script>
element never gets called but I have no idea why. It works fine for the other elements, but never for the second one.
At random times, I get the error Highcharts is not defined
. So the code is trying to use the Highcharts object before it is fully loaded? I thought the onReady
event was supposed to prevent that. Is there something wrong with the way I am using the <Script>
tag? Is there a better way to load an external script than the way I am doing it?