How to update config in HighChart with React hooks? - reactjs

I want to change options of HighChart by useState.
Then I could use a button click to re-render the chart.
Here is my code:
const options = {
title: {
text: "My chart",
},
series: [
{
data: [1, 2, 3, 7, 2, 3, 1, 0],
},
{
data: [1, 2, 1, 7, 3, 3, 3, 0],
},
],
};
export default function HighChartContainer() {
const [chartOptions, setChartOptions] = useState(options);
const handleChangeOptions = () => {
const newLineData = [
{
data: [1, 2, 1, 4, 2, 3, 7, 0],
},
{
data: [1, 2, 1, 6, 3, 5, 2, 0],
},
];
const newOptions = { ...chartOptions, series: newLineData };
setChartOptions(newOptions);
};
return (
<div>
<ChartWrapper>
<HighchartsReact
highcharts={Highcharts}
options={chartOptions}
/>
</ChartWrapper>
<div>
<button type="button" onClick={handleChangeOptions}>
Click me
</button>
</div>
</div>
);
}
It didn't work. I tried google but couldn't find anyone who wrote code in this pattern.
Is there anything wrong?
My code sandbox:
https://codesandbox.io/s/serene-framework-jjoh4?file=/src/App.js

You need to remove handleChangeOptions from the callback to see the effect. Additionally it is enough to update the chart with only new options:
const handleChangeOptions = () => {
const newLineData = [
{
data: [...]
},
{
data: [...]
}
];
const newOptions = { series: newLineData };
setChartOptions(newOptions);
};
Live demo: https://codesandbox.io/s/angry-johnson-gpv99?file=/src/App.js
Docs: https://github.com/highcharts/highcharts-react#optimal-way-to-update

After you update question. I saw the problem is you pass callback={handleChangeOptions} in HighchartsReact. So you just remove it. everything will work normally.

Related

HighCharts Series Space

Hi im trying to remove an empty space between series.
Im using Highcharts with react.
I got a dynamic chart area and i need to remove the gap between the walls.
See the example below
Link: https://jsfiddle.net/dczv0u53/13/
Code:
xAxis: {
tickPositions: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
},
series: [
{
groupPadding: 1,
type: `area`,
data: [
[0, 1],
[1, 3],
[2, 2],
],
states: {
inactive: {
opacity: 0.8
}
}
},
{
type: `area`,
groupPadding: 1,
data: [
[5, 4],
[8, 3],
[10, 3]
],
states: {
inactive: {
opacity: 0.8
}
}
},
]
});
You can use breaks:
xAxis: {
...,
breaks: [{
from: 2,
to: 5,
breakSize: 0
}]
}
Live demo: https://jsfiddle.net/BlackLabel/tb4czrfx/
API Reference: https://api.highcharts.com/highcharts/xAxis.breaks

Getting the max for an Api json data in react

I have fetched an Api data which is "PRICES" , and I'm trying to get the maximum for it but this function is not working , I would appreciate any help !
const pricedata = {
datasets: [
{
backgroundColor: '#0000',
barPercentage: 2,
barThickness: 5,
data: PRICES,
label: 'Update in prices',
maxBarThickness: 10
},
],
};
function findMax(PRICES) {
if (!PRICES) {
return;
}
return Math.max(...PRICES);
}
console.log(findMax())
I added the "price data" where you have PRICES in the data and added a 2nd chunk of data for illustration.
The code below loops over each "dataset" and, finds the max price and adds it as a new key called "maxPrice". Then it prints them out. This is just one way.
const pricedata = {
datasets: [
{
backgroundColor: "#0000",
barPercentage: 2,
barThickness: 5,
data: [1, 10, 30, 7, 42, 12],
label: "Update in prices",
maxBarThickness: 10
},
{
backgroundColor: "#0000",
barPercentage: 2,
barThickness: 5,
data: [11, 70, 18, 17, 24, 12],
label: "Update in prices",
maxBarThickness: 10
}
]
};
function findMax(PRICES) {
if (!PRICES) {
return 0;
}
return Math.max(...PRICES);
}
pricedata.datasets.forEach((dataset) => {
dataset.maxPrice = findMax(dataset.data);
});
pricedata.datasets.forEach((dataset) => {
console.log('max price is', dataset.maxPrice);
});
Update:
Use a reducer to get the max of all the products...
const maxOfAllProducts = pricedata.datasets.reduce((accumulator, current) => Math.max(current.maxPrice, accumulator),0);
console.log('max of all products', maxOfAllProducts)
You forgot to put the PRICES variable in the function call on the last line
let PRICES = [1,2,3,4,5,6,7,8,9,10];
function findMax(PRICES) {
if (!PRICES) {
return;
}
return Math.max(...PRICES);
}
console.log(findMax(PRICES)) // outputs 10
OR remove that variable in the function
let PRICES = [1,2,3,4,5,6,7,8,9,10];
function findMax() {
if (!PRICES) {
return;
}
return Math.max(...PRICES);
}
console.log(findMax()) //outputs 10

React JS Chart JS 2 is not hiding the grid lines in the background

I am building a web application using React JS. My application needs to display some chart widgets on the dashboard. I am using React JS Chart 2 Package, https://www.npmjs.com/package/react-chartjs-2. I can display the chart but there is a problem with styling. I am trying to hide the grid lines in the background. It looks something like this now.
This is my code.
const data = {
labels: ['1', '2', '3', '4', '5', '6'],
datasets: [
{
label: '# of Red Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: '#6886b4',
},
{
label: '# of Blue Votes',
data: [2, 3, 20, 5, 1, 4],
backgroundColor: '#9db8d7',
},
{
label: '# of Green Votes',
data: [3, 10, 13, 15, 22, 30],
backgroundColor: '#112c61',
},
],
};
const options = {
scales: {
yAxes: [
{
gridLines: {
display:false
}
}
],
xAxes: [
{
gridLines: {
display:false
}
}
],
},
};
const GroupedBarChartWidget = (props) => {
return (
<>
<div className={"box"}>
<div className={"box-content"}>
<Bar data={data} options={options} />
</div>
</div>
</>
)
}
As you can see I am trying to hide the gridlines in the background using this.
gridLines: {
display:false
}
But they are still there. What is wrong with my code and how can I fix it?
you are using V2 syntax with V3,
your config needs to be like this:
options: {
scales: {
x: {
grid: {
display: false
}
},
y: {
grid: {
display: false
}
}
}
}
For all differences please read the migration guide

Why is my radio input can only choose one option in the whole array list of array map?

This is the third time I'm asking because the 2 posts previous don't have any answers, because I'm really really stuck on this problem for a whole few days, so please pardon me, because I have tried a few ways but it keeps not work like what I wanted.
Can someone help me out, I use an array map to list out all of my questions and answers based on question_ID
Here's the json that I use Axios to get it.
[
{
"question_ID": 13,
"question_desc": "This is question 1",
"quiz_ID": 6,
"answer_ID": 17,
"answer_desc": "A",
"answer_result": 1
},
{
"question_ID": 13,
"question_desc": "This is question 1",
"quiz_ID": 6,
"answer_ID": 18,
"answer_desc": "B",
"answer_result": 1
},
{
"question_ID": 13,
"question_desc": "This is question 1",
"quiz_ID": 6,
"answer_ID": 19,
"answer_desc": "C",
"answer_result": 0
},
{
"question_ID": 14,
"question_desc": "Question 2",
"quiz_ID": 6,
"answer_ID": 20,
"answer_desc": "A",
"answer_result": 0
},
{
"question_ID": 14,
"question_desc": "Question 2",
"quiz_ID": 6,
"answer_ID": 21,
"answer_desc": "B",
"answer_result": 1
},
{
"question_ID": 15,
"question_desc": "Question 3",
"quiz_ID": 6,
"answer_ID": 22,
"answer_desc": "A",
"answer_result": 1
},
{
"question_ID": 15,
"question_desc": "Question 3",
"quiz_ID": 6,
"answer_ID": 23,
"answer_desc": "B",
"answer_result": 0
},
{
"question_ID": 16,
"question_desc": "Question 4",
"quiz_ID": 6,
"answer_ID": 24,
"answer_desc": "A",
"answer_result": 0
},
{
"question_ID": 16,
"question_desc": "Question 4",
"quiz_ID": 6,
"answer_ID": 25,
"answer_desc": "B",
"answer_result": 1
},
{
"question_ID": 17,
"question_desc": "Question 5",
"quiz_ID": 6,
"answer_ID": 26,
"answer_desc": "Testing Answer",
"answer_result": 0
}
]
Then I use reduce to group the questions, for example, it'll like Question_ID: 13, Question_ID: 14, each of it'll have a list of answer inside there
What's displayed in the console is when I'm pressing the next button, it'll go back and forth in the array of the grouped questions that I've just used to reduce to group it.
Then I map that grouped questions to display a radio input, it displays perfectly fine.
But the problem is when I click on the radio, for example Question 1, I choose 1 of 3 answers that I want
Then I'm pressing next to go to the next index of the grouped array to display a new list of answers
, and I repeat choose the answers that I want
The problem starts to appear, if I press Previous to change or to look back at the previous answers, it disappears, as I can only choose 1 in my list of array, please help and explain because I don't understand.
The previous answers disappear
Here are my code:
import axios from "axios";
import { Component } from "react";
import jwt_decode from "jwt-decode";
class QuizForm extends Component {
constructor(props) {
super(props);
this.state = {
step: 0,
dataQuestion: [],
question: 1,
answer: [],
groupQues: [],
selectedRadio: null,
};
}
// ------------------
// Get list of questions + answers based on the Topic ID//
async componentDidMount() {
await axios
.get("http://localhost:3000/quiz/" + this.props.quizID)
.then((res) => {
this.setState({
dataQuestion: res.data,
});
})
// Then I group it like the picture from above using reduce//
.then(() => {
let grouped = this.state.dataQuestion.reduce((acc, obj) => {
const property = obj["question_ID"];
acc[property] = acc[property] || [];
acc[property].push(obj);
return acc;
}, {});
this.setState({ groupQues: grouped });
})
.then(() => {
var token = localStorage.token;
var decoded = jwt_decode(token);
})
.catch((error) => console.log(error));
}
// -------------------
// The next and previous button to handle switching index in the array //
handleNext = (answer_ID) => {
let grouped = this.state.dataQuestion.reduce((acc, obj) => {
const property = obj["question_ID"];
acc[property] = acc[property] || [];
acc[property].push(obj);
return acc;
}, {});
if (this.state.step === Object.keys(grouped).length - 1) {
return;
}
this.setState({ step: this.state.step + 1 });
this.setState({ question: this.state.question + 1 });
// this.setState({selectedRadio: null})
};
handlePrevious = (answer_ID) => {
if (this.state.step === 0) {
return;
}
this.setState({ step: this.state.step - 1 });
this.setState({ question: this.state.question - 1 });
};
handleSelect = (answer_ID) => {
this.setState({ selectedRadio: answer_ID });
};
// ---------------------
render() {
const { question } = this.state;
return (
<>
<div className="column middle">
<div className="game-details-container">
<h1>
{" "}
Question : <span id="question-number"></span>
{question}/{Object.keys(this.state.groupQues).length}
</h1>
</div>
<div className="game-quiz-container">
{
this.state.groupQues[
Object.keys(this.state.groupQues)[this.state.step]
]?.[0].question_desc
}
<div className="game-options-container">
// In here I'm using map to get list out an array of answers based on question_ID
{this.state.groupQues[
Object.keys(this.state.groupQues)[this.state.step]
]?.map((element, i) => {
return (
<div key={i}>
<input
type="radio"
checked={this.state.selectedRadio === element.answer_ID}
onChange={() => this.handleSelect(element.answer_ID)}
id={element.answer_ID}
name={element.question_desc}
value={element.answer_result}
/>
<label htmlFor={element.question_desc}>
{element.answer_desc}
</label>
</div>
);
})}
</div>
<div className="next-button-container">
<button onClick={() => this.handlePrevious()}>
Previous Question
</button>
<button onClick={() => this.handleNext()}>Next Question</button>
</div>
</div>
</div>
</>
);
}
}
export default QuizForm;
If I understand correctly, you want to accurately keep track of the user's answers to all questions (even across 'next question' and 'previous question' clicks).
However, in handleSelect, you overwrite this.state.selectedRadio with the user's current submission (meaning you lose track of what the user previously may have clicked).
A better approach might be to restructure your component's data/state into an array, where each item (in the array) is an object that contains:
the question data
the options for that particular question
the answer selected by the user for that particular question
Each object could look something like:
{
question_ID: number,
question_desc: string,
quiz_ID: string,
answers: [
{
answer_ID: number,
answer_desc: string,
answer_result: number,
},
],
answerSubmittedByUser: number, // This is where you keep track of what the user answered for this question. For example, you can store `answer_ID` here.
}
Then, you could also have an index or cursor (as component state) to keep track of which question/object the user is currently on. That way, when the user clicks Next question or Previous question, you just increment/decrement the index (whilst also ensuring the new index does not fall outside of the array's bounds). Similarly, if they select an answer, you know which index (in the array) to update.
Here's an example implementation of QuizForm. Although it doesn't use class-based components, it should still give you an idea.
import React, { useEffect, useState } from 'react';
export default function QuizForm() {
const [questions, setQuestions] = useState([]);
const [questionIndex, setQuestionIndex] = useState(0);
useEffect(() => {
let hasUnmounted = false;
async function fetchTransformAndSetQuestionsAsState() {
const questions = await getDataForQuiz();
if (hasUnmounted) {
return;
}
const groupedByQuestionId = questions.reduce((acc, obj) => {
const property = obj['question_ID'];
acc[property] ?? (acc[property] = []);
acc[property].push(obj);
return acc;
}, {});
const readiedForState = Object.values(groupedByQuestionId).map(questionData => {
return {
question_ID: questionData[0].question_ID,
question_desc: questionData[0].question_desc,
quiz_ID: questionData[0].quiz_ID,
answers: questionData.map(question => {
return {
answer_ID: question.answer_ID,
answer_desc: question.answer_desc,
answer_result: question.answer_result,
};
}),
answerSubmittedByUser: null,
};
});
setQuestions(readiedForState);
}
fetchTransformAndSetQuestionsAsState();
return () => {
hasUnmounted = true;
};
}, []);
if (0 === questions.length) {
return <p>Loading questions... just a moment please.</p>;
}
const currentQuestion = questions[questionIndex];
const [isFirstQuestion, isLastQuestion] = [0 === questionIndex, questions.length - 1 === questionIndex];
const handlePreviousQuestionClick = () => {
setQuestionIndex(prev => Math.max(0, prev - 1));
};
const handleNextQuestionClick = () => {
setQuestionIndex(prev => Math.min(questions.length - 1, prev + 1));
};
const handleAnswer = (questionId, answerId) => {
setQuestions(prev => {
/**
* It's probably okay to use questionIndex directly. The ternary and IF check below are probably unnecessary.
* Have written like this just in case we close over a stale `questionIndex`, even though that's probably very unlikely.
*/
const indexToUpdate = prev[questionIndex].question_ID === questionId ? questionIndex : prev.findIndex(({ question_ID }) => questionId === question_ID);
if (-1 !== indexToUpdate) {
return [...prev.slice(0, indexToUpdate), { ...prev[indexToUpdate], answerSubmittedByUser: answerId }, ...prev.slice(indexToUpdate + 1)];
}
return prev; // Unlikely that this line will ever run.
});
};
return (
<>
<div className="column middle">
<div className="game-details-container">
<h1>
<span>Question: </span>
<span id="question-number">{questionIndex + 1}</span>
<span>{`/${questions.length}`}</span>
</h1>
</div>
<div className="game-quiz-container">
<p>{currentQuestion.question_desc}</p>
<div className="game-options-container">
{currentQuestion.answers.map(answer => {
return (
<div key={answer.answer_ID}>
<input
type="radio"
checked={answer.answer_ID === currentQuestion.answerSubmittedByUser}
onChange={() => handleAnswer(currentQuestion.question_ID, answer.answer_ID)}
id={answer.answer_ID}
name={answer.question_desc}
value={answer.answer_result}
/>
<label htmlFor={answer.answer_ID}>{answer.answer_desc}</label>
</div>
);
})}
</div>
<div className="next-button-container">
<button onClick={handlePreviousQuestionClick} disabled={isFirstQuestion}>
Previous Question
</button>
<button onClick={handleNextQuestionClick} disabled={isLastQuestion}>
Next Question
</button>
</div>
</div>
</div>
</>
);
}
async function getDataForQuiz() {
return [
{ question_ID: 13, question_desc: 'This is question 1', quiz_ID: 6, answer_ID: 17, answer_desc: 'A', answer_result: 1 },
{ question_ID: 13, question_desc: 'This is question 1', quiz_ID: 6, answer_ID: 18, answer_desc: 'B', answer_result: 1 },
{ question_ID: 13, question_desc: 'This is question 1', quiz_ID: 6, answer_ID: 19, answer_desc: 'C', answer_result: 0 },
{ question_ID: 14, question_desc: 'Question 2', quiz_ID: 6, answer_ID: 20, answer_desc: 'A', answer_result: 0 },
{ question_ID: 14, question_desc: 'Question 2', quiz_ID: 6, answer_ID: 21, answer_desc: 'B', answer_result: 1 },
{ question_ID: 15, question_desc: 'Question 3', quiz_ID: 6, answer_ID: 22, answer_desc: 'A', answer_result: 1 },
{ question_ID: 15, question_desc: 'Question 3', quiz_ID: 6, answer_ID: 23, answer_desc: 'B', answer_result: 0 },
{ question_ID: 16, question_desc: 'Question 4', quiz_ID: 6, answer_ID: 24, answer_desc: 'A', answer_result: 0 },
{ question_ID: 16, question_desc: 'Question 4', quiz_ID: 6, answer_ID: 25, answer_desc: 'B', answer_result: 1 },
{ question_ID: 17, question_desc: 'Question 5', quiz_ID: 6, answer_ID: 26, answer_desc: 'Testing Answer', answer_result: 0 },
];
}
Side note: You could encapsulate the question fetching and transforming logic into a custom React hook (named something like useQuiz), which would make the fetching/transforming logic more testable whilst also making your component leaner (since the actual logic will move from the component to the custom hook).

Passing an array of objects through props in React

(Solved): Solution was compatibility issues between chart.js and the react version. The recent update to the react version solved it.
I was having trouble finding the solution to this, maybe someone will know why this error is happening:
I am passing an object array via props, but I'm getting an error when it comes to using the prop:
Object array sample (The data for score is fed in through an API and is working):
var teamData = [
{
teamName: "Cowboys",
score: 0,
color: "rgba(0, 34, 68, 0.9)",
},
{
teamName: "Cardinals",
score: 0,
color: "rgba(135, 0, 39, 0.9)",
},
{
teamName: "Patriots",
score: 0,
color: "rgba(0, 21, 50, 0.9)",
},
{
teamName: "49ers",
score: 0,
color: "rgba(170, 0, 0.9)",
},
App.js example
const App = () => {
getData();
return (
<div>
<Header />
<BarChart teamData={teamData} />
<Calculator teamData={teamData} />
<Footer />
</div>
);
};
export default App;
And here is where I pass the props to insert one by one into a chart.js component({props[0].score} is where the error is, it says: SyntaxError C:\Users\name\Documents\Websites\React Based\madden-ranker\src\components\BarChart.js: Unexpected token, expected "," (14:37):
import React from "react";
import { Bar, HorizontalBar } from "react-chartjs-2";
const BarChart = (props) => {
return (
<HorizontalBar
// prettier-ignore
data={{
labels: ['Team1' ],
datasets: [
{
label: 'Cumulative Score',
data: [{props[0].score}, 19, 3, 5, 2, 3, 100, 19, 3, 5, 2, 3, 5, 2, 3, 100, 19, 3, 5, 2, 3, 100, 19, 3, 5, 2, 3, 5, 2, 3, 10, 20],
backgroundColor: []
},
],
}}
height={500}
width={600}
options={{
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
},
},
}}
/>
);
};
export default BarChart;
Interestingly, I tried console logging it inside the teamData component and got this:
line5: console.log(props);
line6: console.log(props.teamData[0].score);
.
Just add a condition to check if props[0] exists before rendering, cause the first time the props is empty, and when you setstate after getting data from API the component rerenders. Hopefully this helps.
I think you had a few commas in places that they shouldn't be
Try this
const BarChart = (props) => {
return (
<HorizontalBar
// prettier-ignore
data={{
labels: ['Team1' ],
datasets: [
{
label: 'Cumulative Score',
data: [{props[0].score}, 19, 3, 5, 2, 3, 100, 19, 3, 5, 2, 3, 5, 2, 3, 100, 19, 3, 5, 2, 3, 100, 19, 3, 5, 2, 3, 5, 2, 3, 10, 20],
backgroundColor: []
}
]
}}
height={500}
width={600}
options={{
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
}
}
}}
/>
);
};

Resources