Why are multiple canvases being made in my Phaser/React app? - reactjs

I am trying to develop a phaser3 application with React. I am just setting up the first canvas for the Phaser.Game. Here is my App.js from the create-react-app.
import "./App.css";
import Phaser, { Game } from "phaser";
import PhaserMatterCollisionPlugin from "phaser-matter-collision-plugin";
import { useCallback, useEffect, useState } from "react";
function App() {
const [game, setGame] = useState(null);
// Creating game inside a useEffect in order to ensure 1 instance is created
console.log("before use effect");
useEffect(() => {
console.log("Going into useEffect");
console.log(game);
if (game) {
console.log("game detected. stop creation");
return;
}
const phaserGame = new Phaser.Game({
width: 512,
height: 412,
backgroundColor: "#333333",
type: Phaser.AUTO,
parent: "survival-game",
scene: [],
scale: {
zoom: 2,
},
physics: {
default: "matter",
matter: {
debug: true,
gravity: { y: 0 },
},
},
plugins: {
scene: [
{
plugin: PhaserMatterCollisionPlugin,
key: "matterCollision",
mapping: "matterCollision",
},
],
},
});
setGame(true);
return;
}, [game]);
}
export default App;
I used useEffect() with useState in order to prevent multiple game instances, but for some reason I am still getting a duplicate canvas and can see that it is running through the useEffect multiple times. console.log of the react app

You should use a ref instead of state for the game object. Here's a small custom hook that sets up a Phaser.Game based on a given configuration:
function usePhaserGame(config) {
const phaserGameRef = React.useRef(null);
React.useEffect(() => {
if (phaserGameRef.current) {
return;
}
phaserGameRef.current = new Game(config);
return () => {
phaserGameRef.current.destroy(true);
phaserGameRef.current = null;
};
}, [] /* only run once; config ref elided on purpose */);
return phaserGameRef.current;
}
const config = {
width: 512,
height: 412,
backgroundColor: '#333333',
type: Phaser.AUTO,
parent: 'survival-game',
scene: [],
scale: {
zoom: 2,
},
physics: {
default: 'matter',
matter: {
debug: true,
gravity: {y: 0},
},
},
plugins: {
scene: [
{
plugin: PhaserMatterCollisionPlugin,
key: 'matterCollision',
mapping: 'matterCollision',
},
],
},
};
function App() {
const game = usePhaserGame(config);
}

Related

update options without reseting the zoom

I am using React ApexChart in order to show off some data, and I have a switch that shows and hides the data labels. The options are changing without problems, but the zoom level is resetting every time. Is there a way to keep the zoom level when changing the options?
Here is a small preview of the component:
import Chart from "react-apexcharts";
import React, { useEffect, useState } from 'react';
import { Layout } from "antd";
interface Props {
series?: ApexAxisChartSeries;
chartOptiosn?: Record<string, any>;
[ key: string ]: any;
}
export const DynamicChart: React.FC<Props> = React.memo( ( props: Props ) => {
const { Content } = Layout;
const { series, chartOptions, uuid } = props;
const [ initialRerender, setInitialRerender ] = useState( false );
useEffect( () => {
if ( !initialRerender ) {
setInitialRerender( true );
}
}, [ initialRerender ] );
useEffect( () => {
}, [] );
useEffect( () => {
console.log( chartOptions );
}, [ chartOptions ] );
return (
<Layout>
<Content>
<Chart
options={ { ...structuredClone( chartOptions || {} ), chart: { ...chartOptions.chart, id: uuid } } }
series={ series }
type="area"
height={ 350 }
/>
</Content>
</Layout>
);
} );
Here are the options:
export const defaultOptions: ApexCharts.ApexOptions = {
series: [],
chart: {
animations: {
enabled: true,
easing: "linear",
dynamicAnimation: {
speed: 300
}
},
toolbar: {
show: true,
tools: {
download: `<svg viewBox="64 64 896 896" focusable: "false" name= "download" theme="outlined"><path d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"></path></svg>`,
selection: false,
zoom: true,
zoomin: true,
zoomout: true,
pan: true,
customIcons: []
},
},
zoom: {
enabled: true
}
},
dataLabels: {
enabled: false
},
yaxis: {
labels: {
style: {
fontSize: "14px",
fontWeight: 600,
colors: [ "#8c8c8c" ],
},
},
},
xaxis: {
type: "datetime"
}
};
Here is a demo for more depth of the problem that I am facing.
Just zoom in between the 3 seconds interval and after the labels reappear, the zoom is refreshed. Is there a way to stop that in react, or should I seek a vanilla approach and ditch the react solution all together?

Function does not work on first render instead the series is generated after the subsequent renders

My function generateSeriesDataWithColor() seems like it does not load before the component or page renders.
So, the seriesWithColor should get the data genrated by generateSeriesDataWithColor() right away when the component is loaded but it does not get generated at the first render, instead if the component is rendered again, the colors and the graph shows up.
import HighchartsReact from "highcharts-react-official";
import Highcharts from "highcharts";
import './SkillsGraph.scss';
import { Col, Row } from "react-bootstrap";
import HeadingMain from "../../Heading/HeadingMain/HeadingMain";
export default function SkillsGraph(){
const skills = ['HTML5/CSS3/JS', 'Java11', 'PHP', 'MySql', 'MongoDB', 'ReactJS', 'ExpressJS'];
const series = {
name: 'Skill Level',
data: [ 10, 9.5, 7, 9.5, 8, 8.5, 8]
};
const seriesWithColor = generateSeriesDataWithColor(series); // This is where the series is assigned to the var
// Randomly generate colors
function generateRandomColor(){
let maxVal = 0xFFFFFF; // 16777215
let randomNumber = Math.random() * maxVal;
randomNumber = Math.floor(randomNumber);
randomNumber = randomNumber.toString(16);
let randColor = randomNumber.padStart(6, 0);
return `#${randColor.toUpperCase()}`
}
// Generate the data with random conlor
function generateSeriesDataWithColor(seriesData){
const data = seriesData.data;
const dataArray = data.map((item) => {
let color = generateRandomColor();
while(color === "#FFFFFF"){
color = generateRandomColor();
}
let dataObj = {
y: item,
color: color
}
return dataObj;
})
let seriesWithColor = {
name: 'Skill Level',
data: dataArray
}
return seriesWithColor; //This is from where the data/series is returned
}
// Options for the graph
let options = {
chart: {
type: 'bar',
height: 400
},
title: {
align: 'left',
text: 'Skills represented'
},
xAxis: {
categories: skills,
visible: true,
type: 'Skills categorised',
title: {
text: null
}
},
yAxis: {
min: 0,
max: 10,
title: {
text: 'Skill Level',
align: 'high'
},
labels: {
overflow: 'justify'
}
},
plotOptions: {
bar: {
dataLabels: {
enabled: false
}
},
column: {
colorByPoint: true
}
},
colors: [
'#ff0000',
'#00ff00',
'#0000ff',
'#0000ff',
'#0000ff',
'#0000ff',
'#0000ff'
],
legend: {
enabled: true
},
credits: {
enabled: false
},
series: seriesWithColor // This is where the generated data/series is used
}
return (
<Row>
<Col md={3}>
<HeadingMain name="This is Legend"></HeadingMain>
</Col>
<Col md={9}>
<HighchartsReact highcharts={Highcharts} options={options} className="chart"></HighchartsReact>
</Col>
</Row>
)
}
Does anyone have a solution for this?
I tried using useEffect hook to complete the wanted task but it gives an error message - 'React Hook useEffect has missing dependencies: 'generateSeriesDataWithColor' and 'series'. Either include them or remove the dependency array react-hooks/exhaustive-deps'. (Please check the code below)
const [seriesWithColor, setSeries] = useState(null);
useEffect(() => {
generateSeriesDataWithColor(series)
.then(data => setSeries(data))
.catch(err => console.log(err));
}, []);
Series needs to be an array of objects instead of a single object:
let options = {
...,
series: [seriesWithColor] // This is where the generated data/series is used
};
Live demo: https://codesandbox.io/s/highcharts-react-demo-h4r493?file=/demo.jsx
API Reference: https://api.highcharts.com/highcharts/series
You will need to call the function within useEffect hook to make sure that the data is available.

Can't perform a React state update on an unmounted component, using class component and component did mount

I have certain code as below:-
class BarChart extends Component {
constructor(){
super();
this.state = {
chartData:{}
}
}
componentDidMount() {
this.getChartData();
}
getChartData() {
axios.get("http://localhost:5001/inventory/clusterscount").then(res => {
const myresponse = res.data;
console.log(myresponse)
let countcluster = [];
let listregion = [];
for (const dataobj of myresponse){
countcluster.push(parseInt(dataobj.clusterscount));
listregion.push(dataobj.region);
}
console.log(countcluster)
console.log(listregion)
this.setState({
chartData: {
labels:listregion,
datasets: [
{
label: "level of thiccness",
data: countcluster,
backgroundColor: ["rgba(75, 192, 192, 0.6)"],
borderWidth: 4
}
]
}
});
});
}
render(){
return (
<div className="App">
<h1>Dankmemes</h1>
<div>
<Line
data={this.state.chartData}
options={{
responsive: true,
title: { text: "THICCNESS SCALE", display: true },
scales: {
yAxes: [
{
ticks: {
autoSkip: true,
maxTicksLimit: 10,
beginAtZero: true
},
gridLines: {
display: false
}
}
],
xAxes: [
{
gridLines: {
display: false
}
}
]
}
}}
/>
</div>
</div>
);
}
}
export default BarChart;
Now while running it am getting the desired clusters and regions as below:-
0: {clusterscount: '2', region: 'test1'}
1: {clusterscount: '10', region: 'test2'}
2: {clusterscount: '8', region: 'test3'}
3: {clusterscount: '1', region: 'test4'}
4: {clusterscount: '8', region: 'test5'}
5: {clusterscount: '2', region: 'test6'}
I am able to get results for clustercount and listregion as well, but keep getting this error. I have tried multiple things but out of ideas.
But in logs am getting as below:-
Can someone help me with this?
The react useEffect expects a cleanup function to cancel subscription and asynchronus tasks so we need to check if component is mounted or not there are couple of ways we can do it and react community have good solution for that.
const LineChart = () =>{
const [chartData,setChartData]= useState({});
const [myresponse, setmyresponse] =useState([]);
const isMountedRef = useRef(null);
useEffect(() =>{
isMountedRef.current = true;
let countcluster = [];
let listregion = [];
axios.get("http://localhost:5001/inventory/clusterscount").
then(res=>{
if(isMountedRef.current){
const myresponse= res.data;
setmyresponse(myresponse)
console.log(myresponse);
for (const dataobj of myresponse){
countcluster.push(parseInt(dataobj.clusterscount));
listregion.push(dataobj.region);
}
setChartData({
labels: listregion,
datasets: [
{
label: "level of thiccness",
data: countcluster,
backgroundColor: ["rgba(75, 192, 192, 0.6)"],
borderWidth: 4
}
]
});
}
})
.catch(err=>{
console.log(err);
});
return () => isMountedRef.current = false;
},[])

Reverse the order of data from an API call works with console.log

Building a Covid tracker using React and ChartJS-2 I found that I couldn't reverse the incoming data through usual means ie. through the recommendations in the chartJS docs. What seems to work is console.log(res.data.data.reverse())
Without this line of code printing to the console, the data displays the opposite way around.
Why is this?
The xAxes params don't take effect, either, but I'm probably just missing a '}' or ']', somewhere.
import { useState, useEffect } from 'react';
import { Line } from 'react-chartjs-2';
import axios from 'axios';
const CovidCases = () => {
const [chartData, setChartData] = useState({});
const chart = () => {
//set variables to pass in as dynamic data
let covCase = [];
let covDate = [];
// axios get endpoint
axios
.get(
'https://api.coronavirus.data.gov.uk/v1/data?filters=areaName=England;areaType=nation&structure={"date":"date","name":"areaName","code":"areaCode","newCasesByPublishDate":"newCasesByPublishDate","cumCasesByPublishDate":"cumCasesByPublishDate","newDeaths28DaysByPublishDate":"newDeaths28DaysByPublishDate","cumDeaths28DaysByPublishDate":"cumDeaths28DaysByPublishDate"}',
)
.then((res) => {
// here I reverse the order that the data comes into the console
console.log(res.data.data.reverse());
for (const dataObj of res.data.data) {
covCase.push(parseInt(dataObj.newCasesByPublishDate));
covDate.push(parseInt(dataObj.date));
// setting the chart data STATE with the chart.js labels and datasets with data: covCase variable
setChartData({
// https://www.chartjs.org/docs/latest/getting-started/usage.html for layout requirements
labels: covDate,
datasets: [
{
label: 'New Covid Cases by Date (England)',
data: covCase,
backgroundColor: ['rgba(81, 250, 157, 0.6)'],
borderWidth: 2,
},
],
});
}
})
.catch((err) => {
console.log(err);
}).catch((err) => {
console.log(err.message)
})
// console.log(covCase, covDate);
};
useEffect(() => {
chart();
}, []);
return (
<div className="charts" style={{ height: 700, width: 900 }} >
<Line
// passing in the STATE as the data to be rendered
data={chartData}
// chart.js options parameters
options={{
title: { text: 'Covid Cases', display: true },
scales: {
yAxes: [
{
ticks: {
autoSkip: true,
beginAtZero: true,
},
},
],
xAxes: [
{
autoSkip: true,
padding: 10,
// this section didn't make the difference expected of it. reversing the console.log() did, though!!
// ticks: {
// // reverse: true,
// maxTicksLimit: 7,
// display: false,
// },
},
],
},
}}
/>
</div>
);
};
export default CovidCases;
Also, the xAxes parameters were taking no effect but I think I'm within the code recommendations?
Thanks.

Cytoscape and ReactJS integration

I am trying to use use Cytoscape with ReactJS and some how nothing is getting displayed in the simple component i am trying.
Here is the code. I am returning an empty object in mapStateToProps as i am trying to display a static graph where i have hard coded the edges and nodes.
Cytoscape version i am using is from my package.json
"cytoscape": "^2.7.6",
"react": "^15.2.1",
Here is the Cyctoscape jsbin i am using for my sample.
enter link description here
Appreciate any help.
Thanks
import React,{Component} from 'react';
import cytoscape from 'cytoscape';
import {connect} from 'react-redux';
import { bindActionCreators } from 'redux';
class GraphContainer extends React.Component{
constructor(props){
super(props);
this.renderCytoscapeElement = this.renderCytoscapeElement.bind(this);
}
renderCytoscapeElement(){
console.log('* Cytoscape.js is rendering the graph..');
this.cy = cytoscape(
{
container: document.getElementById('cy'),
boxSelectionEnabled: false,
autounselectify: true,
style: cytoscape.stylesheet()
.selector('node')
.css({
'height': 80,
'width': 80,
'background-fit': 'cover',
'border-color': '#000',
'border-width': 3,
'border-opacity': 0.5,
'content': 'data(name)',
'text-valign': 'center',
})
.selector('edge')
.css({
'width': 6,
'target-arrow-shape': 'triangle',
'line-color': '#ffaaaa',
'target-arrow-color': '#ffaaaa',
'curve-style': 'bezier'
})
,
elements: {
nodes: [
{ data: { id: 'cat' } },
{ data: { id: 'bird' } },
{ data: { id: 'ladybug' } },
{ data: { id: 'aphid' } },
{ data: { id: 'rose' } },
{ data: { id: 'grasshopper' } },
{ data: { id: 'plant' } },
{ data: { id: 'wheat' } }
],
edges: [
{ data: { source: 'cat', target: 'bird' } },
{ data: { source: 'bird', target: 'ladybug' } },
{ data: { source: 'bird', target: 'grasshopper' } },
{ data: { source: 'grasshopper', target: 'plant' } },
{ data: { source: 'grasshopper', target: 'wheat' } },
{ data: { source: 'ladybug', target: 'aphid' } },
{ data: { source: 'aphid', target: 'rose' } }
]
},
layout: {
name: 'breadthfirst',
directed: true,
padding: 10
}
});
}
componentDidMount(){
this.renderCytoscapeElement();
}
render(){
return(
<div className="node_selected">
<div style="{height:'400px';width:'400px'}" id="cy"/>
</div>
)
}
}
function mapStateToProps(state){
return {};
}
export default connect(mapStateToProps,null)(GraphContainer);
For those still looking for how to get the code posted working - I was able to get it working by modifying the cy div, specifying its style with non-zero height and width.
render() {
let cyStyle = {
height: '1000px',
width: '1000px',
margin: '20px 0px'
};
return (
<div>
<div style={cyStyle} id="cy"/>
</div>
);
}
I had to integrate cytoscape.js with react as well, but no redux, so wrote a gist in case anybody else needs it.
code example
I am able to resolve the Issue. I missed the Styles for the Layer itself and hence it is defaulting to 0px height and 0px width.
Thanks
For a basic version of the above using the cytoscape "Getting Started" example and using React hooks you can do this:
import React, {Fragment, useEffect, useRef} from 'react';
import cytoscape from 'cytoscape'
const GraphTest = () => {
const graphRef = useRef(null)
const drawGraph = () => {
const cy = cytoscape({
container: graphRef.current,
elements: [
{ data: { id: 'a' } },
{ data: { id: 'b' } },
{
data: {
id: 'ab',
source: 'a',
target: 'b'
}
}]
})
}
useEffect(() => {
drawGraph()
}, [])
return (
<Fragment>
<h2>Graph Test</h2>
<div ref={graphRef} style={{width: '100%', height: '80vh'}}>
</div>
</Fragment>
)
}
export default GraphTest

Resources