setting state with onChange in React results in canvas being null - javascript

I have a simple application consisting of two - parent App and child Circle components. My aim is to draw circle on canvas (Circle component does that) and have input that takes number value and makes angle of that numerical value with the radius. like so :
example
The problem is, i've set up onChange event listener with handler on input, which is supposed to update my state, but whenever i type something into input field, it tells me that my canvas is null, and .getContext("2d") method can't be performed, which messes up entire application.
I've googled and found out that this is because of the fact that canvas renders after state is updated, or something like that. I can't think of any solution that would make that possible. here's my code :
App.js
import React, { Component } from "react";
import "./App.css";
import { degreesToRadiansFlipped } from "./helpers/helpers";
import Circle from "./components/Circle";
class App extends Component {
state = { degrees: 0 };
handleChange = (event) => {
this.setState({ degrees: event.target.value });
};
coordinates = {
x: Math.cos(degreesToRadiansFlipped(120)) * 100 + 150,
y: Math.sin(degreesToRadiansFlipped(120)) * 100 + 150
};
drawCircle = (context, x, y) => {
context.beginPath();
context.arc(150, 150, 100, 0, Math.PI * 2);
context.moveTo(150, 150);
context.lineTo(x, y);
context.stroke();
};
render() {
console.log(this.currentDegreeValue);
return (
<div className="main">
<Circle drawCircle={this.drawCircle} coordinates={this.coordinates} />
<form>
<input name="degrees" type="text" onChange={this.handleChange} />
</form>
</div>
);
}
}
export default App;
Circle.js
import React, { Component } from "react";
class Circle extends Component {
componentDidMount() {
this.props.drawCircle(
this.context,
this.props.coordinates.x,
this.props.coordinates.y
);
}
render() {
return (
<canvas
ref={(canvas) => (this.context = canvas.getContext("2d"))}
width={300}
height={300}
/>
);
}
}
export default Circle;
Actual Error is :
TypeError: Cannot read property 'getContext' of null

// index.js
import React, { Component } from "react";
import ReactDOM from "react-dom";
import Circle from "./Circle";
class App extends Component {
state = { degrees: 0, showCirle: false };
handleChange = event => {
this.setState({ degrees: event.target.value, showCirle: true });
};
degreesToRadiansFlipped(angle) {
return (angle + Math.PI) % (2 * Math.PI);
}
coordinates = {
x: Math.cos(this.degreesToRadiansFlipped(120)) * 100 + 150,
y: Math.sin(this.degreesToRadiansFlipped(120)) * 100 + 150
};
drawCircle = (context, x, y) => {
console.log("inside drawcircle");
context.beginPath();
context.arc(150, 150, 100, 0, Math.PI * 2);
context.moveTo(150, 150);
context.lineTo(x, y);
context.stroke();
};
render() {
console.log(this.currentDegreeValue);
return (
<div className="main">
{this.state.showCirle && (
<Circle drawCircle={this.drawCircle} coordinates={this.coordinates} />
)}
<form>
<input name="degrees" type="text" onChange={this.handleChange} />
</form>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
// Circel Component
import React, { Component } from "react";
class Circle extends Component {
constructor(props) {
super(props);
}
fillContext = () => {
const canvas = this.refs.canvas
const ctx = canvas.getContext("2d")
this.context = ctx
this.props.drawCircle(
this.context,
this.props.coordinates.x,
this.props.coordinates.y
);
}
componentDidMount() {
this.fillContext()
}
componentDidUpdate() {
this.fillContext()
}
render() {
return <canvas ref="canvas" width={300} height={300} />;
}
}
export default Circle;
In your example, x and y coordinates should change based on input value change, but coordinates is an object with fixed x and y values. I think that logic should also change.

Related

Unsure how to unmount scene in #react/three-fiber

I'm building this scene, and I have a few issues.
One small issue is with directionalLight. When I try using more up to date three.js attributes like shadow.camera.far on it, #react-three/fiber loses the ability to call the dispose function on the light's shadows. So, I have to use the deprecated properties, which causes me to get these annoying warnings. That's not the biggest problem though. I can live with that, but I'd also be grateful if anyone had any idea how to get rid of those warnings.
The main problem is that when I try making the model in my scene clickable so that the user can go to a different page in the app, I get a #react-three/fiber error of Uncaught TypeError: Cannot read property 'disconnect' of undefined. I think this is because I'm using the unmountComponentAtNode function in the scene's useEffect hook, and it's causing the component to unmount before #react-three/fiber can properly remove all the threejs objects. However, when I remove that function, I get the common react error of: Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Anyone have any idea what I can do? I'd appreciate any help.
Here's my code:
import React, { Suspense, useRef, useEffect, useState } from 'react'
import {
Canvas,
extend,
unmountComponentAtNode,
useFrame,
useLoader,
useThree,
} from '#react-three/fiber'
import { CubeTextureLoader, DoubleSide, PerspectiveCamera, sRGBEncoding } from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import Model3D from '../../assets/models/model.glb'
import './Model.css'
import nx from '../../assets/environmentMap/nx.jpg'
import ny from '../../assets/environmentMap/ny.jpg'
import nz from '../../assets/environmentMap/nz.jpg'
import px from '../../assets/environmentMap/px.jpg'
import py from '../../assets/environmentMap/py.jpg'
import pz from '../../assets/environmentMap/pz.jpg'
extend({ OrbitControls })
const wallMap = {
'side-panel': '/wall/1',
'panel-material': '/wall/2',
'panel-material-2': '/wall/3',
'side-panel-2': '/wall/4',
entrance: '/wall/5',
'overhead-entrance': '/wall/5',
}
const CameraControls = () => {
const {
camera,
gl: { domElement },
} = useThree()
const controls = useRef()
useFrame(() => controls.current.update())
return <orbitControls ref={controls} args={[camera, domElement]} enableDamping />
}
const SkyBox = () => {
const { scene } = useThree()
const loader = new CubeTextureLoader()
const environmentMap = loader.load([px, nx, py, ny, pz, nz])
environmentMap.encoding = sRGBEncoding
scene.background = environmentMap
scene.environment = environmentMap
return null
}
const Model = ({ changePage }) => {
const active = useRef(false)
const moving = useRef(false)
const material = useRef()
const { scene } = useThree()
const gltf = useLoader(GLTFLoader, Model3D)
const camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100)
camera.position.set(-4.62, 1.3, 4)
scene.add(camera)
const handleClick = (e) => {
if (moving.current) return
const {
material: { name },
} = e.intersections[0].object
changePage(wallMap[name])
}
const handlePointerDown = () => {
active.current = true
}
const handlePointerUp = () => {
if (active.current) {
setTimeout(() => {
moving.current = false
active.current = false
}, 30)
}
}
const handlePointerMove = () => {
if (active.current) {
moving.current = true
}
}
return (
<primitive
object={gltf.scene}
position={[0, -1.25, 0]}
scale={0.22}
rotation={[0, -0.6, 0]}
castShadow
onClick={handleClick}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
>
<meshStandardMaterial attach='material' ref={material} />
</primitive>
)
}
const Plane = () => {
return (
<mesh receiveShadow rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.8, 0]}>
<planeGeometry args={[12.5, 12.5, 2, 2]} />
<meshStandardMaterial color='#222' side={DoubleSide} />
</mesh>
)
}
const Scene = () => {
const [redirectPath, setRedirectPath] = useState('')
useEffect(() => {
if (redirectPath) window.appHistory.push(redirectPath)
return () => {
unmountComponentAtNode(document.querySelector('canvas'))
}
}, [redirectPath])
return (
<div className='webgl'>
<Canvas>
<Suspense fallback={null}>
<ambientLight color='#ffffff' intensity={0.3} />
<directionalLight
position={[-3, 5, 5]}
color='#ffffff'
intensity={3}
castShadow
// Last 4 are deprecated properties that cause warnings from three.js.
// When I use something like shadow={ { bias: 0.05 } }, I lose the dispose
// function from the shadow. So, I'm still using them for now.
shadowBias={0.05}
shadowCameraFar={15}
shadowMapHeight={1024}
shadowMapWidth={1024}
/>
<CameraControls />
<Model changePage={(x) => setRedirectPath(x)} />
<Plane />
<SkyBox />
</Suspense>
</Canvas>
</div>
)
}
export default Scene

How to find out what can be exported from npm package

I'm trying to replicate this https://codepen.io/swizec/pen/bgvEvp
I've installed the d3-timer package with npm https://www.npmjs.com/package/d3-timer
It is definitely there because I have read through the files.
What I am confused about is how to import the timer into my code. In the code on codepen it just uses d3.timer but doesn't show the import above. So I tried importing d3 but it can't find it in the d3-timer package. I tried timer, Timer, D3, d3.
So my question is - how do I go about investigating the package to work out what the names of the exports are?
Or if that is too complicated - in this particular case what should I be importing to get the functionality of d3.timer?
Many thanks!
Code from code pen:
const Component = React.Component;
const Ball = ({ x, y }) => (
<circle cx={x} cy={y} r={5} />
);
const MAX_H = 750;
class App extends Component {
constructor() {
super();
this.state = {
y: 5,
vy: 0
}
}
componentDidMount() {
this.timer = d3.timer(() => this.gameLoop());
this.gameLoop();
}
componentWillUnmount() {
this.timer.stop();
}
gameLoop() {
let { y, vy } = this.state;
if (y > MAX_H) {
vy = -vy*.87;
}
this.setState({
y: y+vy,
vy: vy+0.3
})
}
render() {
return (
<svg width="100%" height={MAX_H}>
<Ball x={50} y={this.state.y} />
</svg>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'));
my code
import React from 'react';
import d3 from 'd3-timer'
const Component = React.Component;
const Ball = ({ x, y }) => (
<circle cx={x} cy={y} r={5} />
);
const MAX_H = 750;
export default class App extends Component {
constructor() {
super();
this.state = {
y: 5,
vy: 0
}
}
componentDidMount() {
this.timer = d3.timer(() => this.gameLoop());
this.gameLoop();
}
componentWillUnmount() {
this.timer.stop();
}
gameLoop() {
let { y, vy } = this.state;
if (y > MAX_H) {
vy = -vy*.87;
}
this.setState({
y: y+vy,
vy: vy+0.3
})
}
render() {
return (
<svg width="100%" height={MAX_H}>
<Ball x={50} y={this.state.y} />
</svg>
)
}
}
Error message:
Attempted import error: 'd3-timer' does not contain a default export (imported as 'd3').
Try
import { timer } from 'd3-timer' and then use timer()

How to make draggable cursor in area chart in victory native

Note: I want a solution for react-native.I want to make a tooltip and a draggable cursor on the area chart in which there is a line which shows the datapoint of the area chart through draggable cursor. I just want to mark the datapoint which is from the data set only not the and y value of touch point of the screen, just only x and y value on the graph when touching on a screen it gives the nearest value of the data set corresponding to the screen.
I search on the internet I found something similar but it was for the react and used a scatter graph as I am very new to victory native library and react-native can someone tell how to make a react-native equivalent of it with the area chart. Here is the link where I was referring to see..
Click me
I just want to make a similar thing with the area chart in victory native with draggable cursor and tooltip which only mark the data point on the chart only not on the screen coordinates.
Can anyone provide me with the react-native code how to implement it as similar to the link provided above as per my requirement with a victory area chart?
Also including react code which I want to convert to react-native.
Please, someone, help me out.
/* Victory requires `react#^15.5.0` and `prop-types#^15.5.0` */
const {
VictoryLine, VictoryChart, VictoryCursorContainer, VictoryLabel, VictoryScatter
} = Victory;
const { range, first, last } = _; // lodash
const mountNode = document.getElementById('app');
const allData = range(750).map((x) => ({x, y: x + 30 * Math.random()}));
const findClosestPointSorted = (data, value) => {
// assumes 3 things:
// 1. data is sorted by x
// 2. data points are equally spaced
// 3. the search is 1-dimentional (x, not x and y)
if (value === null) return null;
const start = first(data).x;
const range = (last(data).x - start);
const index = Math.round((value - start)/range * (data.length - 1));
return data[index];
};
class App extends React.Component {
constructor() {
super();
this.state = {
activePoint: null
};
}
handleCursorChange(value) {
this.setState({
activePoint: findClosestPointSorted(allData, value)
});
}
render() {
const { activePoint } = this.state;
const point = activePoint ?
<VictoryScatter data={[activePoint]} style={{data: {size: 100} }}/>
: null;
return (
<div>
<VictoryChart
containerComponent={
<VictoryCursorContainer
dimension="x"
onChange={this.handleCursorChange.bind(this)}
cursorLabel={cursor => `${activePoint.x}, ${Math.round(activePoint.y)}`}
/>
}
>
<VictoryLine data={allData} style={{data: {stroke: '#999'} }}/>
{point}
</VictoryChart>
</div>
);
}
}
ReactDOM.render(
<App/>,
mountNode
);
import React, { Component } from 'react'
import { Text, StyleSheet, View } from 'react-native'
import {VictoryArea,VictoryChart,createContainer,VictoryTooltip,VictoryScatter,VictoryLine } from 'victory-native';
import {range, first, last,maxBy } from 'lodash';
import Svg,{Line} from 'react-native-svg';
const VictoryZoomVoronoiContainer = createContainer( "cursor","voronoi");
const data = range(20,81).map((x) => ({x, y: x*x}));
const findClosestPointSorted = (data, value) => {
if (value === null) return null;
const start = first(data).x;
const range = (last(data).x - start);
const index = Math.round((value - start)/range * (data.length - 1));
return data[index];
};
export default class Chart extends Component {
componentWillMount()
{
this.setState({ymax:maxBy(data,function(o) { return o.y; }).y})
}
state = {
activePoint:null,
data:data,
ymax :0
}
handleCursorChange(value) {
this.setState({
activePoint: findClosestPointSorted(data, value)
});
}
render() {
const { activePoint } = this.state;
const point = activePoint ?
<VictoryScatter name = "scatter" data={[activePoint]} style={{data: {size: 200,fill:'#ffffff',stroke:'#1bad53',strokeWidth:2} }}/>
: null;
return (
<View>
<VictoryChart
height={300}
width={350}
containerComponent={
<VictoryZoomVoronoiContainer
voronoiDimension="x"
cursorDimension="x"
voronoiBlacklist={["scatter"]}
labelComponent={<VictoryTooltip style={{fill:'red'}} flyoutStyle={{
fill: 'rgba(52, 52, 52, 0.8)',}}/>}
onCursorChange={(value)=>{this.handleCursorChange(value)}}
labels={cursor => {
try {
return(activePoint.x?`${activePoint.x}, ${Math.round(activePoint.y)}\ndjh`:null)
} catch (error) {
console.log(error)
}
}}
/>
}
>
<VictoryArea
name = "area"
data={data}
interpolation="cardinal"
style={{
data: {
fill: '#1bad53',
stroke: '#05a543',
strokeWidth: 2
}
}}
/>
{point}
{activePoint?<Line x1= {50} x2="300" y1={250-(200/this.state.ymax)*activePoint.y} y2={250-(200/this.state.ymax)*activePoint.y} stroke="black" strokeWidth="1"/>:null}
</VictoryChart>
</View>
)
}
}

React hooks: referencing the parent of a component

I'm creating a custom mouse cursor per component (e.g. a custom mouse cursor on a figure element). I'm writing a custom hook for this. This is the hook so far:
const useMouseCoords = () => {
let [coords, setCoords] = useState({ x: 0, y: 0 })
// I need to calculate the coordinates from the parents offset. Here is where I'm stuck.
let offsetParent = parentRef.current.getBoundingClientRect()
function handleCoords(e) {
setCoords({
x: e.clientX - offsetParent.x,
y: e.clientY - offsetParent.y,
})
}
useEffect(() => {
if (typeof window === `undefined`) return // escape gatsby build process
window.addEventListener('mousemove', handleCoords)
return () => {
window.removeEventListener('mousemove', handleCoords)
}
}, [])
return coords
}
The mousecursor component is quite simple:
const MouseCursor = (props) => {
let { x, y } = useMouseCoords()
return (
<div
className="mouse-cursor-button"
style={{
transform: `translate(${x}px, ${y}px)`,
}}
>
<div className="mouse-cursor-button__text">Click to play</div>
</div>
)
}
Code of course doesn't work but is rather to illustrate what I'm trying to achieve.
So I need the parent of the MouseCursor component to calculate the offset. I'm stuck at the part where I want to reference the parent component. I was hoping I could pass it as an argument to the hook.
So the question is how can I access the parent component in the hook?
Can you not just pass the ref down like:
function Parent() {
const ref = useRef()
return <div ref={ref}><MouseCursor parentRef={ref} /></div>
}
const MouseCursor = (props) => {
let { x, y } = useMouseCoords(props.parentRef)
return (
<div
className="mouse-cursor-button"
style={{
transform: `translate(${x}px, ${y}px)`,
}}
>
<div className="mouse-cursor-button__text">Click to play</div>
</div>
)
}
See https://codesandbox.io/s/0y46034oyl?fontsize=14 for example
You can have a function in the parent component that interacts with it's state and then the uses its state in the props of the children.
class Parent extends Component{
constructor(props){
super(props);
this.state = {
x: 0,
y:0
};
}
//...
function setCoords(x, y){
this.setState({
x: x,
y: y
})
}
//...
render(){
return(
<Child
actions={ {setParentCoords: this.setCoords.bind(this)} }
x={ this.state.x }
y={ this.state.y }
)
}
Now in your child you have access to props.actions.setParentCoords()

Victory: Animating Circular Progress Bar - not rendering chart

I'm working to use Victory's charting library with React to render a Animating Circular Progress bar like so:
https://formidable.com/open-source/victory/gallery/animating-circular-progress-bar
Here is my code:
import React from 'react';
import {connect} from 'react-redux';
import {VictoryPie, VictoryAnimation, VictoryLabel} from 'victory';
class YourRatings2 extends React.Component {
constructor() {
super();
this.state = {
percent: 25, data: this.getData(0)
};
}
componentDidMount() {
let percent = 25;
}
getData(percent) {
return [{x: 1, y: percent}, {x: 2, y: 100 - percent}];
}
render() {
return (
<section className="YourRatings">
<h2>Core Skills</h2>
<div>
<svg viewBox="0 0 400 400" width="100%" height="100%">
<VictoryPie
animate={{duration: 1000}}
width={400} height={400}
data={this.state.data}
innerRadius={120}
cornerRadius={3}
labels={() => null}
style={{
data: { fill: (d) => {
const color = d.y > 30 ? "green" : "red";
return d.x === 1 ? color : "transparent";
}
}
}}
/>
<VictoryAnimation duration={1000} data={this.state}>
{(newProps) => {
return (
<VictoryLabel
textAnchor="middle" verticalAnchor="middle"
x={200} y={200}
text={`${Math.round(newProps.percent)}%`}
style={{ fontSize: 45 }}
/>
);
}}
</VictoryAnimation>
</svg>
</div>
</section>
);
}
}
const mapStateToProps = state => {
return {
};
};
export default connect(mapStateToProps)(YourRatings2);
Unfortunately, this is only rendering the text, not the full graph. see:
Any pointers as to what I am doing wrong?
I'm not an expert on this library but the example that you gave has an interval function to update this.state.data with the new percentage. You are setting initial percentage to 0 and not updating.
Setting the interval like the example or setting the initial state with the example below should fix the problem.
Example
constructor() {
super();
this.state = {
percent: 25,
data: this.getData(25)
};
}
I think you may need to add the prop standalone={false} to VictoryPie

Categories

Resources