The Importance of Image Optimisation
June 3, 2024The Power of MySQL Stored Procedures
November 14, 2024As web developers, we handle a variety of projects - A personal project, a project that is intended for a client’s business or a project that helps a community.
In short, we create these projects in different ways, with tools that are currently available. However, in an industry that consistently changes without end, said tools can become deprecated or lose support by its creators.
This is significantly true for Vue 2, which has reached its End of Life, a date when developers of a language will cease offering official support, on 31st December 2023. This would mean it is highly advisable to avoid working with Vue 2 code when starting on a project. But what about old projects?
If you have a project that is still live and being used by multiple users, it is best to gradually update the code so that you will not be facing a situation where a functionality suddenly stopped working. If the project has been gathering dust for two or three years, it is highly advisable to update everything, especially if you did not create the project yourself.
In this blog, we will be going over some tips on how you should update an old Vue 2 project to Vue 3.
Index
- Ensure that Component Structure Is Consistent
- Converting Options API to Composition API
- Updating Chart.js Implementations
1. Ensure that Component Structure Is Consistent:
When updating code for an old project, it is inevitable to discover code that was implemented by someone that didn’t fully grasp what could have been done better or didn’t yet learn on different techniques on improving code. This is especially true for component implementation.
If you don’t implement components correctly, you might make it difficult for yourself or other developers.
When looking at the example below, you can see that the component, ActivityModal, was added within a slot tag for the AddModal component:
ActivityView.vue
<AddModal v-if="modalOpen"> <template v-slot:header>Activity Profile</template> <template v-slot:content> <ActivityModal></ActivityModal> </template> <template v-slot:footer> <button class="secondaryBtn w-40" @click="closeModal">Close</button> </template> <template v-slot:background> <div v-if="modalOpen" class="opacity-75 fixed inset-0 z-40 bg-black"></div> </template> </AddModal>
The issue with this component placement is that the ActivityModal component consists of code that is responsible for adding data to the database, where it is possible to see the inputs and save the data, but it also prevents other developers from emitting events to indicate that the data has successfully saved the data because you cannot listen to events with <slot> tags.
The best solution is to copy the code from ActivityModal and paste it in the content template, as well as the functions in the <script> tag. This way, you can easily determine if data has successfully added without dealing with any headaches. So, in short, you will need to have the code look like the following:
ActivityView.vue
<AddModal v-if="modalOpen"> <template v-slot:header>Activity Profile</template> <template v-slot:content> <div class="grid grid-cols-3 mt-5 gap-5"> <div> <base-select v-model="selectedOption" refKey="id" label="Select Data Type" :items="myOptions" item-text="name" item-value="id" ></base-select> </div> <div class="col-span-2"> <div v-if="myForm"> <div class="modalForm"> <h2 class="mb-5 headingTwo">Enter Data Values</h2> <base-date-time-picker v-model="selectedDate" label="Pick a Date" :maxDate="new Date()" required ></base-date-time-picker> <template v-for="(fi, j) in myForm" :key="j"> <div v-if="fi.value_type == 'integer'" class="form-control w-full overflow-y-auto" style="padding: 0.9%; margin-left: -3px; width: 39.7rem;" > <base-input v-model="submitForm[fi.name]" :refKey="fi.name" :label="fi.ui_name" :additionalLabel="true" :additionalLabelValue="fi.unit" required type="number" ></base-input> </div> <div v-else class="form-control w-full"> <base-input v-model="submitForm[fi.name]" refKey="fi.name" :label="fi.ui_name" :additionalLabel="true" :additionalLabelValue="fi.unit" required ></base-input> </div> </template> <div class="flex justify-between"> <div></div> <button class="primaryBtn w-40 mt-5" @click=" addData(); " > Add Data </button> </div> </div> </div> </div> </div> </template> <template v-slot:footer> <button class="secondaryBtn w-40" @click="closeModal">Close</button> </template> <template v-slot:background> <div v-if="modalOpen" class="opacity-75 fixed inset-0 z-40 bg-black" ></div> </template> </AddModal>
It is paramount that you consistently create and place components, as you need to know what is expected to happen on a screen, how data should be transferred between parent and child components, and if it is even necessary to create a component for a one specific screen.
In the next section, we will be delving into the conversion of Vue 2’s Options API to Vue 3’s Composition API.
2. Converting Options API to Composition API
For most old Vue projects and StackOverflow entries that consist of Vue 2 code, you will note that you will find code like the example below:
Routine.vue
<script> import { mapGetters } from "vuex"; import base from "../services/api"; import AddModal from "@/assets/AddModal.vue"; export default { name: "RoutineView", components: { AddModal, }, data() { return { templateList: [], selectedRoutinesList: [], checkedRoutine: [], routine: 0, routineName: "Routine 1", page: 1, addFrom: "", custom: "", editModal: false, routineFormFromList: { selectedRoutines: [], }, routineFormFromCustom: { selectedCustom: [], }, }; }, computed: { routineList() { return this.checkedRoutine; }, ...mapGetters(["selectedRoutines"]), }, mounted() { this.getRoutineTemplates(); this.getSelectedRoutines(); }, methods: { addRoutineTemplate() { console.log( "Selected from List: ", this.routineFormFromList.selectedRoutines ); base .post("/template-routine/copy/", { template_routines: this.routineFormFromList.selectedRoutines, }) .then((response) => { if (response.data?.status === "success") { console.log(response); this.getSelectedRoutines(); } }) .catch((e) => { console.error("Add Routines Error", e); }); this.addFrom = ""; }, addRoutine() { console.log( "Selected Custom Data: ", this.routineFormFromCustom.selectedCustom ); base .post("/routine/create/", { routineCustom: routineFormFromCustom.value.selectedCustom, routine: this.routine++, name: this.routineName, }) .then((response) => { if (response.data?.status === "success") { console.log(response); this.getSelectedRoutines(); } }) .catch((e) => { console.error("Add Data Error", e); }); this.getRoutineTemplates(); this.addFrom = ""; }, getRoutineTemplates() { base .post("/template-routine/get/", { page: this.page, }) .then((response) => { if (response.data?.status === "success") { this.templateList = response.data.data.result; console.log("Templates: ", this.templateList); } }) .catch((e) => { console.error("View profile Error", e); }); }, getSelectedRoutines() { base .get("/routine/get/") .then((response) => { if (response.data?.status === "success") { this.selectedRoutinesList = response.data.data; console.log("My List: ", this.selectedRoutinesList); } }) .catch((e) => { console.error("View profile Error", e); }); }, }, }; </script>
The example above is using Options API, which is the old way of how we processed data and triggered functionalities for Vue projects.
You will also note that there are a lot of console logs in the example - Avoid logging out information in the final product of your code, as it is unwise to log out sensitive data.
It is always a good practice to keep your code clean and readable, so it won’t take other developers, who are taking over the project, 6 months to understand the code. The code needs to look the following below:
Routine.vue
<script setup> import { ref, onMounted, computed } from "vue"; import { useStore } from "vuex"; import { toast } from 'vue3-toastify'; import 'vue3-toastify/dist/index.css'; const store = useStore(); const user = computed(() => store.getters["auth/getUser"]); const routineTimer = ref(0); let templateList = ref([]); let selectedRoutinesList = ref([]); const routine = ref(0); const routineName = ref("Routine 1"); const page = ref(2); const addFrom = ref(); let routineFormFromList = ref({ selectedRoutines: [], }); const routineFormFromCustom = ref({ selectedCustom: [], }); const addRoutineTemplate = async () => { base .post("/template-routine/create/", { template_routines: routineFormFromList.value.selectedRoutines, }) .then((response) => { if (response.data?.status === "success") { addFrom.value = false; toast('Routine Added Successfully!', { type: 'success', position: toast.POSITION.BOTTOM_RIGHT, }); getSelectedRoutines(); } }) .catch((e) => { console.error("Add Routines Error", e); toast('Unable to Add Routine!', { type: 'error', position: toast.POSITION.BOTTOM_RIGHT, }); }); addFrom.value = ""; }; const addRoutine = async () => { base .post("/routine/create/", { routineCustom: routineFormFromCustom.value.selectedCustom, routine: routine.value++, name: routineName.value, }) .then((response) => { if (response.data?.status === "success") { addFrom.value = false; toast('Data Added Successfully!', { type: 'success', position: toast.POSITION.BOTTOM_RIGHT, }); getSelectedRoutines(); } }) .catch((e) => { console.error("Add Data Error", e); toast('Unable to Add Data!', { type: 'success', position: toast.POSITION.BOTTOM_RIGHT, }) }); getRoutineTemplates(); addFrom.value = ""; }; const getRoutineTemplates = async () => { base.post("/template-routine/get/", { page: page, }).then((response) => { if (response.data?.status === "success") { templateList.value = response.data.data.result; } }).catch((e) => { console.error("View profile Error", e); toast('Unable to View Routine!', { type: 'error', position: toast.POSITION.BOTTOM_RIGHT, }); }); }; const getSelectedRoutines = async () => { base .get("/routine/get/") .then((response) => { if (response.data?.status === "success") { selectedRoutinesList.value = response.data.data; } }) .catch((e) => { console.error("View profile Error", e); toast('Unable to View Routine!', { type: 'error', position: toast.POSITION.BOTTOM_RIGHT, }); }); }; onMounted(() => { getRoutineTemplates(); getSelectedRoutines(); }); </script>
You might also have noticed that toast notifications have been added in the newly converted code. The reason being is that the code I am using is supposed to add data on the front-end, but the old code did not notify the user that data was being successfully added, so it was not a user-friendly approach.
It is up to you to decide what type of notification you want to implement, but the main goal is that it must convey necessary information to the average user that they have successfully added, accessed, or deleted data.
Additional Note: Every project consists of different code, serving different purposes. If you are curious on other examples of converting code from Vue 2 to Vue 3, I would recommend looking at Milan Dadhaniya’s comprehensive guide Vue 3 code migration
3. Updating Chart.js Implementations
If the project you are working on consists of Chart.js code that still has old Vue 2 code, you will need to update how the graphs are being generated for it to be fully compatible with your newly updated project.
In the following example, the chart we are looking at is a line chart that consists of multiple tabs to display different data:
Chart.vue
<template> <div class="grid grid-cols-2 gap-5 mb-8"> <div class="bg-white rounded p-5"> <div class="grid grid-cols-4 gap-2"> <h1 class="headingOne text-base self-center font-normal col-span-2">Select Date Range</h1> <button class="secondaryBtn text-xs focus:bg-secondary-700 focus:border-secondary-700 active:bg-secondary-700" @click="getCurrentMonth()" >This Month</button> <button class="primaryBtn text-xs focus:bg-secondary-700 focus:border-secondary-700" @click="getCurrentYear()" >This Year</button> </div> </div> <div class="bg-white rounded p-5"> <div class="grid grid-cols-4 gap-2"> <h1 class="headingOne text-base self-center font-normal col-span-2">Customize Date Range </h1> <DatePicker v-model="fromDate" autoApply /> <DatePicker v-model="toDate" autoApply /> </div> </div> </div> <div class="grid grid-cols-3 gap-2"> <button class="btnTab" @click="getDataType1()"> Skin data1 Data </button> <button class="btnTab" @click="getDataType2()"> Body data22 Index Data </button> <button class="btnTab" @click="getDataType3()"> Calorie Count Data </button> </div> <div class="relative bg-gray-300 rounded-lg rounded-t-none p-10 mb-5" id="chartWrapper"> <canvas id="line-chart"> </canvas> </div> </template> <script> import base from "../../services/api"; import moment from "moment"; import { formatISO } from "date-fns"; import { Chart, registerables } from "chart.js"; Chart.register(...registerables); export default { name: "BodyCharts", mounted: function () { this.$nextTick(function () { this.ctx = document.getElementById("line-chart").getContext("2d"); window.myLine = new Chart(this.ctx, this.chartConfig); this.getCurrentDate(); this.getCurrentYear(); this.getDataType1(); }); }, computed: { chartConfig() { return { type: "line", data: { labels: this.categoriesList, datasets: [], }, options: { maintainAspectRatio: true, responsive: true, title: { display: false, text: "Sales Charts", fontColor: "white", }, legend: { labels: { fontColor: "white", }, align: "end", position: "bottom", }, tooltips: { mode: "index", intersect: false, }, hover: { mode: "nearest", intersect: true, }, scales: { xAxes: [ { ticks: { fontColor: "white", }, display: true, scaleLabel: { display: false, labelString: "Month", fontColor: "white", }, gridLines: { display: false, borderDash: [2], borderDashOffset: [2], color: "rgba(33, 37, 41, 0.3)", zeroLineColor: "rgba(0, 0, 0, 0)", zeroLineBorderDash: [2], zeroLineBorderDashOffset: [2], }, }, ], yAxes: [ { ticks: { fontColor: "white", }, display: true, scaleLabel: { display: false, labelString: "Value", fontColor: "white", }, gridLines: { borderDash: [3], borderDashOffset: [3], drawBorder: false, color: "white", zeroLineColor: "white", zeroLineBorderDash: [2], zeroLineBorderDashOffset: [2], }, }, ], }, }, }; }, }, data() { return { fromDate: "", toDate: "", dataArr: [], arr: [], selected: "", categoriesList: null, dataSeriesList: null, dataSeriesList2: null, datasets: null, ctx: null, }; }, methods: { // date methods getCurrentDate() { var currentDate = new Date(); this.toDate = formatISO(currentDate); console.log("From Date: ", this.toDate); }, getCurrentYear() { var firstDayYear = new Date(new Date().getFullYear(), 0, 1); this.fromDate = formatISO(firstDayYear); console.log("To Date: ", this.fromDate); }, getCurrentMonth() { var firstDayMonth = new Date( new Date().getFullYear(), new Date().getMonth(), 1 ); this.fromDate = formatISO(firstDayMonth); console.log("To Date: ", this.fromDate); }, // render chart methods generateDataSet( label, data, backgroundColor = "#F04248", borderColor = "#F04248" ) { return { label, backgroundColor, borderColor, data, fill: false, }; }, updateChart() { window.myLine.data.labels = this.categoriesList; window.myLine.data.datasets = this.datasets; window.myLine.update(); }, // get data methods getDataType1() { base .post(`/get-datatype-one/`, { date_from: this.fromDate, date_to: this.toDate, }) .then((response) => { if (response.data?.status === "success") { this.dataList = response.data.details; this.categoriesList = []; this.dataSeriesList = []; this.datasets = []; this.dataList.forEach((d) => { const date = moment(d.created_on).format("yyyy-MM-DD"); const { data1 } = d; this.categoriesList.push(date); this.dataSeriesList.push(parseInt(data1)); }); this.datasets.push( this.generateDataSet("DataType1 Label", this.dataSeriesList) ); this.updateChart(); } }) .catch((e) => { console.error("getDataType1 Error", e); }); }, getDataType2() { base .post(`/get-datatype-two/`, { date_from: this.fromDate, date_to: this.toDate, }) .then((response) => { if (response.data?.status === "success") { this.dataList = response.data.data; this.categoriesList = []; this.dataSeriesList = []; this.dataSeriesList2 = []; this.datasets = []; this.dataList.forEach((d) => { const date = moment(d.created_on).format("yyyy-MM-DD"); const { data22, data21 } = d; this.categoriesList.push(date); this.dataSeriesList.push(parseInt(data22)); this.dataSeriesList2.push(parseInt(data21)); }); this.datasets.push( this.generateDataSet("DataType2 Label 2", this.dataSeriesList) ); this.datasets.push( this.generateDataSet( "DataType2 Label 2", this.dataSeriesList2, "#058EF0", "#058EF0" ) ); this.updateChart(); } }) .catch((e) => { console.error("getDataType2 Error", e); }); }, getDataType3() { base .post(`/get-datatype-three/`, { date_from: this.fromDate, date_to: this.toDate, }) .then((response) => { if (response.data?.status === "success") { this.dataList = response.data.data; this.categoriesList = []; this.dataSeriesList = []; this.dataSeriesList2 = []; this.datasets = []; this.dataList.forEach((d) => { const date = moment(d.created_on).format("yyyy-MM-DD"); const { data_value, expected_data_value } = d; this.categoriesList.push(date); this.dataSeriesList.push(parseInt(data_value)); this.dataSeriesList2.push(parseInt(expected_data_value)); }); this.datasets.push( this.generateDataSet("DataType3 Label 1", this.dataSeriesList) ); this.datasets.push( this.generateDataSet( "DataType3 Label 2", this.dataSeriesList2, "#058EF0", "#058EF0" ) ); this.updateChart(); } }) .catch((e) => { console.error("getDataType3 Error", e); }); }, }, }; </script> <style> </style>
What we need to do is to implement the needed functions that would indicate which tab the user has clicked so that it can be avoid to go through all of the tabs when interacting with the filter inputs. We also need to make sure that the functions responsible for retrieving the data are updated accordingly.
To ensure that we are properly generating the graph, we need to re-organize how the old code used to generate the data – We place the new Chart code into a separate function, getting rid of window.myLine and replacing it with let myLine.
The refreshTab function as well as the tab constant values need to be implemented to ensure that the correct data is being displayed every time a user tries to access a tab for the specific data type. We also need to ensure that the function gets triggered when a user interacts with the filter inputs. Do not forget to include the determineCurrentTab function, as it does provide the needed information to determine what tab the user has clicked on.
We then proceed to ensure that the generateDataSet function properly processes through all the information that the array lists consist of, which have been retrieved from the dataList array.
Once all the necessary updates have been made, the code should look something like below:
Chart.vue
<template> <div class="grid grid-cols-2 gap-5 mb-8 font-paragraph-500"> <div class="bg-white rounded p-5"> <div class="grid grid-cols-4 gap-2"> <h1 class="headingOne text-base self-center font-normal col-span-2"> Select Date Range </h1> <button class="secondaryBtn text-xs focus:bg-primary-600 focus:border-primary-600 active:bg-primary-600" @click="getCurrentMonth()" > This Month </button> <button class="primaryBtn text-xs focus:bg-primary-600 focus:border-primary-600" @click="getCurrentYear()" > This Year </button> </div> </div> <div class="bg-white rounded p-5"> <div class="grid grid-cols-3 gap-2"> <base-date-time-picker v-model="fromDate" :maxDate="new Date()" > </base-date-time-picker> <base-date-time-picker v-model="toDate" :maxDate="new Date()" > </base-date-time-picker> <button class="primaryBtn text-xs focus:bg-primary-600 focus:border-primary-600" @click="customizeDateRange()" > Customize Date Range </button> </div> </div> </div> <div class="grid grid-cols-3 gap-2 mb-4"> <button class="btnTab" @click="getDataType1(); determineCurrentTab('DataType1')"> Skin Fold Data </button> <button class="btnTab" @click="getDataType2(); determineCurrentTab('DataType2')"> Body Mass Index Data </button> <button class="btnTab" @click="getDataType3(); determineCurrentTab('DataType3')"> Calorie Count Data </button> </div> <div class="relative bg-gray-300 rounded-lg rounded-t-none p-10 mb-5" id="chartWrapper"> <canvas id="line-chart"> </canvas> </div> </template> <script setup> import { ref, onMounted, computed, nextTick } from "vue"; import { useStore } from "vuex"; import base from "../../services/api"; import { formatISO } from "date-fns"; import moment from "moment"; import { Chart, registerables } from "chart.js"; import BaseDateTimePicker from "../../../src/assets/Form/BaseDateTimePicker.vue"; Chart.register(...registerables); const store = useStore(); const user = computed(() => store.getters["auth/getUser"]); const formatDate = (date) => { return moment(date).format("YYYY-MM-DD HH:mm"); // Consistent date formatting }; const fromDate = ref(""); const toDate = ref(""); const graphData = {}; const dataList = ref([]); const categoriesList = ref(null); const dataSeriesList = ref(null); const dataSeriesList2 = ref(null); const datasets = ref(null); const ctx = ref(null); const dataType1Tab = ref(true); const dataType2Tab = ref(false); const dataType3Tab = ref(false); const chartConfig = computed(() => { return { type: "line", // Define the type of chart data: { labels: categoriesList.value, // Use dynamic list of labels (dates) datasets: datasets.value, // Dynamic datasets }, options: { maintainAspectRatio: true, responsive: true, scales: { x: [{ type: 'time', time: { unit: 'second', displayFormats: { second: 'YYYY-MM-DD HH:mm' } }, grid: { drawBorder: false, display: true, color: "#ffffff", }, ticks: { fontColor: "white", }, }], y: { grid: { drawBorder: false, display: true, color: "#ffffff", }, ticks: { fontColor: "white", }, }, }, title: { display: false, }, legend: { labels: { fontColor: "white", }, align: "end", position: "bottom", }, tooltips: { mode: "index", // Tooltip mode intersect: false, }, hover: { mode: "nearest", // Hover behavior intersect: true, }, }, }}); let myLine = null; const generateGraph = async (ctx, chartConfig) => { myLine = new Chart(ctx, chartConfig); } const getCurrentDate = async () => { const currentDate = new Date(); toDate.value = formatISO(currentDate); } const getCurrentYear = async () => { const firstDayYear = new Date(new Date().getFullYear(), 0, 1); fromDate.value = formatISO(firstDayYear); refreshTabs(); }; const customizeDateRange = async () => { refreshTabs(); }; const getCurrentMonth = async () => { const firstDayMonth = new Date( new Date().getFullYear(), new Date().getMonth(), 1 ); fromDate.value = formatISO(firstDayMonth); refreshTabs(); }; const generateDataSet = async (label, data, backgroundColor = "#F04248", borderColor = "#F04248") => { graphData.value = { label: label, backgroundColor: backgroundColor, borderColor: borderColor, data: data, fill: false } }; const updateChart = async () => { myLine.data.labels = categoriesList.value; myLine.data.datasets = datasets.value; myLine.update(); }; const determineCurrentTab = async (tabType) => { /* This function is reponsible ensuring that the user does not get taken to a tab they do not wish to view when adding data to customizing dates */ if(tabType == "DataType1"){ dataType1Tab.value = true; dataType2Tab.value = false; dataType3Tab.value = false; } if(tabType == "DataType2"){ dataType1Tab.value = false; dataType2Tab.value = true; dataType3Tab.value = false; } if(tabType == "DataType3"){ dataType1Tab.value = false; dataType2Tab.value = false; dataType3Tab.value = true; } } const refreshTabs = async () => { /* This function is reponsible for refreshing graphs when a user adds data or selects date ranges This is to ensure that data can instantly retrieved without the user needing to refresh the page everytime */ if(dataType1Tab.value == true){ getDataType1(); } if(dataType2Tab.value == true){ getDataType2(); } if(dataType3Tab.value == true){ getDataType3(); } }; const getDataType1 = async () => { base .post(`/get-datatype-one/`, { date_from: fromDate.value, date_to: toDate.value, }) .then((response) => { if (response.data?.status === "success") { dataList.value = response.data.details; categoriesList.value = []; dataSeriesList.value = []; datasets.value = []; dataList.value.forEach((d) => { const date = formatDate(d.created_on); const { skin_fold } = d; categoriesList.value.push(date); dataSeriesList.value.push(parseInt(skin_fold)); }); generateDataSet("DataType1 Label",, dataSeriesList) datasets.value.push( graphData.value ); updateChart(); } }) .catch((e) => { console.error("getDataType1 Error", e); }); }; const getDataType2 = async () => { base .post(`/get-datatype-two/`, { date_from: fromDate.value, date_to: toDate.value, }) .then((response) => { console.log(response); if (response.data?.status === "success") { dataList.value = response.data.data; categoriesList.value = []; dataSeriesList.value = []; dataSeriesList2.value = []; datasets.value = []; dataList.value.forEach((d) => { const date = formatDate(d.created_on); const { body_mass, body_height } = d; categoriesList.value.push(date); dataSeriesList.value.push(parseInt(body_mass)); dataSeriesList2.value.push(parseInt(body_height)); }); generateDataSet("DataType2 Label 1", dataSeriesList); datasets.value.push( graphData.value ); generateDataSet( "DataType2 Label 2", dataSeriesList2, "#058EF0", "#058EF0" ); datasets.value.push( graphData.value ); updateChart(); } else { console.log('No data present for getDataType2') } }) .catch((e) => { console.error("getDataType2 Error", e); }); }; const getDataType3 = async () => { base .post(`/get-datatype-three/`, { date_from: fromDate.value, date_to: toDate.value, }) .then((response) => { if (response.data?.status === "success") { dataList.value = response.data.data; categoriesList.value = []; dataSeriesList.value = []; dataSeriesList2.value = []; datasets.value = []; dataList.value.forEach((d) => { const date = formatDate(d.created_on); const { calorie_value, expected_calorie_value } = d; categoriesList.value.push(date); dataSeriesList.value.push(parseInt(calorie_value)); dataSeriesList2.value.push(parseInt(expected_calorie_value)); }); generateDataSet("DataType3 Label 1", dataSeriesList); datasets.value.push( graphData.value ); generateDataSet( "DataType3 Label 2", dataSeriesList2, "#058EF0", "#058EF0" ); datasets.value.push( graphData.value ); updateChart(); } }) .catch((e) => { console.error("getDataType3 Error", e); }); }; onMounted(() => { nextTick(function () { ctx.value = document.getElementById("line-chart").getContext("2d"); generateGraph(ctx.value, chartConfig.value); getCurrentDate(); getCurrentYear(); getDataType1(); }); }); </script> <style> #chart-wrapper { display: inline-block; position: relative; width: 100%; height: 2000px; } .chart { background-color: #efefef; } </style>
If you need the graph to be responsive after adding data, which would usually show in the graph, it is best to use the following code for the parent component the graph is in.
<ActivityCharts :key="activityChartKey"></ActivityCharts> ... <script> const activityChartKey = ref(0); // Refreshes the entirey of all tabs for the charts component const forceRerender = () => { activityChartKey.value += 1; }; ... .then((response) => { console.log(response); if (response.data?.status === "success") { toast('Data Successfully Added!', { type: 'success', position: toast.POSITION.BOTTOM_RIGHT, }); closeModal(); forceRerender(); } }) ... </script>
I hope that this blog has given you some idea on what to aim for when updating old Vue project. I wish nothing but success for your endeavors.