I'm currently working on a project using Ruby on Rails and React. I'm using Vite vite_rails and vite version 5.3.1. I'm trying to integrate some dynamic charts using Apexcharts examples from Flowbite.
As shown in the image below when I run vite dev
and start the server I see the chart. But when I run vite build
the donut chart doesn't seem to compile.
Im unable to identify why. My hunch is that there must be an issue with the configuration of the Vite config.
import { defineConfig } from 'vite';
import RubyPlugin from 'vite-plugin-ruby';
import svgr from 'vite-plugin-svgr';
export default defineConfig({
plugins: [
RubyPlugin(),
svgr({
svgrOptions: {
exportType: 'named',
ref: true,
svgo: false,
titleProp: true,
},
include: '**/*.svg',
}),
muteWarningsPlugin(warningsToIgnore),
],
build: {
minify: false,
terserOptions: {
compress: {
keep_infinity: true,
drop_console: false, // Keep console.log() statements
},
},
sourcemap: true, // Ensure sourcemaps are enabled
},
});
Based on this example, for the Donut Chart from Flowbite is that Im setting it.
import React, { useEffect, useRef, useState } from 'react';
import ApexCharts from 'apexcharts';
import LoadingSpinner from './LoadingSpinner';
const DonutChart = () => {
const chartRef = useRef(null);
const [series, setSeries] = useState([0, 0, 0]);
const [loading, setLoading] = useState(true);
const getChartOptions = (stats) => {
return {
series: series,
colors: ["#022963", "#e74694", "#FDBA8C"],
chart: {
height: 320,
width: "100%",
type: "donut",
},
stroke: {
colors: ["transparent"],
lineCap: "",
},
plotOptions: {
pie: {
donut: {
labels: {
show: true,
name: {
show: true,
fontFamily: "Inter, sans-serif",
offsetY: 20,
},
total: {
showAlways: true,
show: true,
label: "Total Assets",
fontFamily: "Inter, sans-serif",
formatter: function () {
return stats.total_assets;
},
},
value: {
show: true,
fontFamily: "Inter, sans-serif",
fontWeight: 'bold',
offsetY: -20,
formatter: function (value) {
return value + "%"
},
},
},
size: "80%",
},
},
},
grid: {
padding: {
top: -2,
},
},
labels: ["Operational", "Not Operational", "Under repair"],
dataLabels: {
enabled: false,
},
legend: {
position: "bottom",
fontFamily: "Inter, sans-serif",
},
yaxis: {
labels: {
formatter: function (value) {
return value + "%"
},
},
},
xaxis: {
labels: {
formatter: function (value) {
return value + "%"
},
},
axisTicks: {
show: false,
},
axisBorder: {
show: false,
},
},
}
}
useEffect(() => {
let chartInstance = null;
const fetchData = async () => {
try {
const response = await fetch('/api/v1/equipment_stats');
const stats = await response.json();
const newSeries = [
stats.operational || 0,
stats.not_operational || 0,
stats.under_repair || 0,
];
setSeries(newSeries);
setLoading(false);
if (!chartInstance && chartRef.current) {
const chartOptions = getChartOptions(stats);
chartOptions.series = newSeries.filter(n => n !== null && n !== undefined); // Ensure no null/undefined values
chartInstance = new ApexCharts(chartRef.current, chartOptions);
chartInstance.render();
} else if (chartInstance) {
chartInstance.updateSeries(newSeries);
}
} catch (error) {
console.error("Error fetching data:", error);
setLoading(false);
// Optionally, handle the UI here for error state
}
};
fetchData();
return () => {
if (chartInstance) {
chartInstance.destroy();
}
};
}, []);
if (loading) {
return <LoadingSpinner />;
}
return (
<div className="py-4">
<h2 className="text-center font-semibold my-4">Equipment Health</h2>
<div ref={chartRef}></div>
</div>
);
}
export default DonutChart;
here is other related information
{
"name": "aviato",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"serve": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@headlessui/react": "^1.7.15",
"@heroicons/react": "^2.0.18",
"@mui/icons-material": "^5.15.10",
"@mui/material": "^5.15.10",
"@rails/activestorage": "^7.0.6",
"@stripe/react-stripe-js": "^2.4.0",
"@stripe/stripe-js": "^2.4.0",
"@tailwindcss/forms": "^0.5.3",
"activestorage": "^5.2.8-1",
"apexcharts": "^3.44.0",
"clsx": "^2.1.0",
"crypto-js": "^4.2.0",
"react": "^18.2.0",
"react-currency-input-field": "^3.6.11",
"react-dom": "^18.2.0",
"react-hook-form": "^7.48.2",
"react-router-dom": "^6.14.0",
"tailwindcss": "^3.3.2"
},
"devDependencies": {
"autoprefixer": "^10.4.14",
"postcss": "^8.4.24",
"tailwindcss": "^3.3.2",
"vite": "^5.3.1",
"vite-plugin-ruby": "^5.0.0",
"vite-plugin-svgr": "^4.2.0"
},
"volta": {
"node": "20.10.0"
}
}
This is a bug I been fighting with for a couple of weeks. I been raised the issue with the Vite team. https://github.com/vitejs/vite/issues/17516
vite_rails (3.0.17)
railties (>= 5.1, < 8)
vite_ruby (~> 3.0, >= 3.2.2)
I'm not sure how your example works when you run vite dev
. I tried it with a simpler example so I can run it, it didn't work in either case. You have a bit of a race condition between when useEffect
is called and when chartRef.current
is set.
optional
dependencies
: The list of all reactive values referenced inside of the setup code. Reactive values include props, state, and all the variables and functions declared directly inside your component body.
...
If you omit this argument, your Effect will re-run after every re-render of the component.
https://react.dev/reference/react/useEffect#parameters
If you log chartRef.current
it is null
on the first render. You must be triggering a rerender somewhere for this set up to work.
Declaring loading
as a dependency was enough for me:
useEffect(() => {
let chartInstance = null;
setLoading(false);
console.log(chartRef); // initial: {current: null}
// rerender: {current: div}
if (!chartInstance && chartRef.current) {
chartInstance = new ApexCharts(chartRef.current, getChartOptions({}));
chartInstance.render();
}
return () => {
if (chartInstance) {
chartInstance.destroy();
}
};
}, [loading]);
// ^^^^^^^