Can i call a specific function inside `useEffect` in React? - reactjs

I have a canvas component in react. I am using useEffect to get the canvas element. So i have defined all needed functions in useEffect, as you can see below
import { useEffect, useRef } from "react"
import * as blobs2Animate from "blobs/v2/animate"
export const CornerExpand = () => {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const canvas = canvasRef.current!
const ctx = canvas.getContext("2d")
const animation = blobs2Animate.canvasPath()
const width = canvas.clientWidth * window.devicePixelRatio
const height = canvas.clientHeight * window.devicePixelRatio
canvas.width = width
canvas.height = height
const renderAnimation = () => {
if (!ctx) return
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = "#E0F2FE"
ctx.fill(animation.renderFrame())
requestAnimationFrame(renderAnimation)
}
requestAnimationFrame(renderAnimation)
const size = Math.min(width, height) * 1
const defaultOptions = () => ({
blobOptions: {
seed: Math.random(),
extraPoints: 36,
randomness: 0.7,
size,
},
canvasOptions: {
offsetX: -size / 2.2,
offsetY: -size / 2.2,
},
})
const loopAnimation = () => {
animation.transition({
duration: 4000,
timingFunction: "ease",
callback: loopAnimation,
...defaultOptions(),
})
}
animation.transition({
duration: 0,
callback: loopAnimation,
...defaultOptions(),
})
const fullscreen = () => {
const options = defaultOptions()
options.blobOptions.size = Math.max(width, height) * 1.6
options.blobOptions.randomness = 1.4
options.canvasOptions.offsetX = -size / 2
options.canvasOptions.offsetY = -size / 2
animation.transition({
duration: 2000,
timingFunction: "elasticEnd0",
...options,
})
}
}, [canvasRef])
return (
<div>
<canvas className="absolute top-0 left-0 h-full w-full" ref={canvasRef} />
</div>
)
}
export default CornerExpand
Everything works as well, but now I have a problem. I want to execute the fullscreen() function when a button is clicked in the parent component. Since I have defined the function in useEffect, I can't call it directly, isn't it? What can I do to solve this?

You can do it like this.
export const CornerExpand = React.forwardRef((props, ref) => {
//....
{
//...
const fullscreen = () => {
const options = defaultOptions()
options.blobOptions.size = Math.max(width, height) * 1.6
options.blobOptions.randomness = 1.4
options.canvasOptions.offsetX = -size / 2
options.canvasOptions.offsetY = -size / 2
animation.transition({
duration: 2000,
timingFunction: "elasticEnd0",
...options,
})
}
ref.current = fullscreen;
}, [canvasRef]);
You can wrap this comp with React.forwardRef. And call it from parent.
<CornerExpand ref={fullscreenCallbackRef} />
And then call like this
fullscreenCallbackRef.current()

Related

How to use canvas HTML5 on smartphone

I have code written on react js, using canvas html 5. It is image eraser: it should properly erase front image and when only some persent of it left, it trigger another action(i haven not done another action yet). It works fine in browser, but when i choose adaptive for any smatphone, it stops working. I tried to change mouse events to touch events but it didn't help.
Here is the code:
import './DrawingCanvas.css';
import {useEffect, useRef, useState} from 'react';
const DrawingCanvas = () => {
const canvasRef = useRef(null);
const contextRef = useRef(null);
const [isDrawing, setIsDrawing] = useState(false);
const [persent, setPersent] = useState(100);
useEffect(() => {
const url = require('../../images/one.jpg');
const canvas = canvasRef.current;
const img = new Image();
img.src = url;
const context = canvas.getContext("2d");
img.onload = function () {
let width = Math.min(500, img.width);
let height = img.height * (width / img.width);
canvas.width = width;
canvas.height = height;
context.drawImage(img, 0, 0, width, height);
contextRef.current = context;
context.lineCap = "round";
context.lineWidth = 350;
contextRef.current.globalCompositeOperation = 'destination-out';
};
}, []);
useEffect(() => {
console.log(persent);
if(persent<=0.1) return alert('hello')
}, [persent]);
const startDrawing = ({nativeEvent}) => {
const {offsetX, offsetY} = nativeEvent;
contextRef.current.beginPath();
contextRef.current.moveTo(offsetX, offsetY);
contextRef.current.lineTo(offsetX, offsetY);
// contextRef.current.stroke();
setIsDrawing(true);
nativeEvent.preventDefault();
};
const draw = ({nativeEvent}) => {
if (!isDrawing) {
return;
}
const {offsetX, offsetY} = nativeEvent;
contextRef.current.lineTo(offsetX, offsetY);
contextRef.current.stroke();
nativeEvent.preventDefault();
let pixels = contextRef.current.getImageData(0, 0, canvasRef.current.width, canvasRef.current.height);
let transPixels = [];
for (let i = 3; i < pixels.data.length; i += 4) {
transPixels.push(pixels.data[i]);
}
let transPixelsCount = transPixels.filter(Boolean).length;
setPersent((transPixelsCount / transPixels.length) * 100);
};
const stopDrawing = () => {
contextRef.current.closePath();
setIsDrawing(false);
};
return (
<div>
<canvas className="canvas-container"
ref={canvasRef}
onMouseDown={startDrawing}
onMouseMove={draw}
onMouseUp={stopDrawing}
onMouseLeave={stopDrawing}
>
</canvas>
</div>
)
}
export default DrawingCanvas;
.canvas-container {
background-image: url("https://media.istockphoto.com/id/1322277517/photo/wild-grass-in-the-mountains-at-sunset.jpg?s=612x612&w=0&k=20&c=6mItwwFFGqKNKEAzv0mv6TaxhLN3zSE43bWmFN--J5w=");
}
Maybe it all can be done through touch events, but idk how to do that properly

Rendering Threejs in React dissapears my root element in HTML

I'm trying to use Three.js in Typescript react, I render Dodecahedron figure and random stars, I want to add some mark up to my three.js with React but when I render Three.js canvas into HTML it dissapears my root div, and I'm not able to add some other components
THREE.JS
import * as THREE from "three";
export function ThreeCanvas() {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.innerHTML = "";
document.body.appendChild(renderer.domElement);
const geometry = new THREE.DodecahedronBufferGeometry(1.7, 0);
const material = new THREE.MeshStandardMaterial({
color: "#00FF95",
});
const Stars = () => {
const starGeometry = new THREE.SphereGeometry(0.1, 24, 24)
const starMaterial = new THREE.MeshStandardMaterial({color: 0xffffff})
const star = new THREE.Mesh(starGeometry, starMaterial)
const [x, y, z] = Array(3).fill(1).map(() => THREE.MathUtils.randFloatSpread(70))
star.position.set(x, y, z)
scene.add(star)
}
Array(200).fill(100).forEach(Stars)
const light = new THREE.PointLight(0xffffff)
light.position.set(20, 20, 20)
scene.add(light)
camera.position.z = 5;
camera.position.x = 3.5
const Figure = new THREE.Mesh(geometry, material);
scene.add(Figure);
const animate = () => {
requestAnimationFrame(animate);
Figure.rotation.x += 0.01;
Figure.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
window.addEventListener("resize", () => {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
});
return null
}
this is my three.js code what I do in react
React
import ReactDOM from "react-dom"
import { ThreeCanvas } from "./Components/Three";
import Landing from "./Components/Landing";
import "./Style/Style.css"
const FirstSection = () => {
return (
<div className="container">
<Landing />
<ThreeCanvas />;
</div>
);
}
ReactDOM.render(<FirstSection />, document.getElementById("root"));
Landing is my markup component when I open console I dont see anywhere my Landing element but in react tools I see, how to fix that issue I have no idea
You're removing document.body in Three.JS component which is why the body only contains the canvas. You might want to use a reference to the element instead of targeting document.body so that it's not disturbing the DOM structure which is why your markdown does not show. As a rule of thumb, you should never be interacting with the DOM via the document.
document.body.innerHTML = "";
I've quickly refactored the Three.JS component to use a React element reference so that you can add additional markup.
Refactored ThreeCanvas Component
import * as React from "react";
import * as THREE from "three";
export function ThreeCanvas() {
const ref = React.useRef();
const [loaded, setLoaded] = React.useState(false);
React.useEffect(() => {
if (!loaded && ref) {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
const geometry = new THREE.DodecahedronBufferGeometry(1.7, 0);
const material = new THREE.MeshStandardMaterial({
color: "#00FF95"
});
const Stars = () => {
const starGeometry = new THREE.SphereGeometry(0.1, 24, 24);
const starMaterial = new THREE.MeshStandardMaterial({
color: 0xffffff
});
const star = new THREE.Mesh(starGeometry, starMaterial);
const [x, y, z] = Array(3)
.fill(1)
.map(() => THREE.MathUtils.randFloatSpread(70));
star.position.set(x, y, z);
scene.add(star);
};
Array(200).fill(100).forEach(Stars);
const light = new THREE.PointLight(0xffffff);
light.position.set(20, 20, 20);
scene.add(light);
camera.position.z = 5;
camera.position.x = 3.5;
const Figure = new THREE.Mesh(geometry, material);
scene.add(Figure);
const animate = () => {
requestAnimationFrame(animate);
Figure.rotation.x += 0.01;
Figure.rotation.y += 0.01;
renderer.render(scene, camera);
};
const resize = () => {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
};
animate();
ref.current.appendChild(renderer.domElement);
window.addEventListener("resize", resize);
setLoaded(true);
return () => window.removeEventListener("resize", resize);
}
}, [ref, loaded]);
return <div ref={ref} />;
}
export default ThreeCanvas;
Typescript Version: ThreeCanvas.tsx
import * as React from "react";
import * as THREE from "three";
export function ThreeCanvas() {
const ref = React.useRef<HTMLDivElement>(null);
const [loaded, setLoaded] = React.useState(false);
React.useEffect(() => {
if (!loaded && ref.current) {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
const geometry = new THREE.DodecahedronBufferGeometry(1.7, 0);
const material = new THREE.MeshStandardMaterial({
color: "#00FF95"
});
const Stars = () => {
const starGeometry = new THREE.SphereGeometry(0.1, 24, 24);
const starMaterial = new THREE.MeshStandardMaterial({
color: 0xffffff
});
const star = new THREE.Mesh(starGeometry, starMaterial);
const [x, y, z] = Array(3)
.fill(1)
.map(() => THREE.MathUtils.randFloatSpread(70));
star.position.set(x, y, z);
scene.add(star);
};
Array(200).fill(100).forEach(Stars);
const light = new THREE.PointLight(0xffffff);
light.position.set(20, 20, 20);
scene.add(light);
camera.position.z = 5;
camera.position.x = 3.5;
const Figure = new THREE.Mesh(geometry, material);
scene.add(Figure);
const animate = () => {
requestAnimationFrame(animate);
Figure.rotation.x += 0.01;
Figure.rotation.y += 0.01;
renderer.render(scene, camera);
};
const resize = () => {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
};
animate();
ref.current.appendChild(renderer.domElement);
window.addEventListener("resize", resize);
setLoaded(true);
return () => window.removeEventListener("resize", resize);
}
}, [ref, loaded]);
return <div ref={ref} />;
}
export default ThreeCanvas;

How to stop page scrolling on a div React

I have a map element using an svg, and have functions to zoom and pan, however the zoom function, which uses the mouse wheel, wont stop the entire page from scrolling when the mouse is inside the map. I have tried to use the e.preventDefault call to stop this, however I keep getting the error Unable to preventDefault inside passive event listener invocation. I have been unable to find a solution to this that works. I know I am probably missing something small, but I have had no success in making it work.
Here is the code minus the stuff inside the SVG as it is too much to fit in the post.
import React, { useEffect, useRef, useState } from 'react'
export default function Map () {
const [isPanning, setPanning] = useState(false)
const [map, setMap] = useState()
const [position, setPosition] = useState({
oldX: 0,
oldY: 0,
x: 0,
y: 0,
z: 1,
})
const containerRef = useRef()
const onLoad = (e) => {
setMap({
width: 1000,
height: 1982
})
}
const onMouseDown = (e) => {
e.preventDefault()
setPanning(true)
setPosition({
...position,
oldX: e.clientX,
oldY: e.clientY
})
}
const onWheel = (e) => {
if (e.deltaY) {
const sign = Math.sign(e.deltaY) / 10
const scale = 1 - sign
const rect = containerRef.current.getBoundingClientRect()
console.log(map)
setPosition({
...position,
x: position.x * scale - (rect.width / 2 - e.clientX + rect.x) * sign,
y: position.y * scale - (1982 * rect.width / 1000 / 2 - e.clientY + rect.y) * sign,
z: position.z * scale,
})
}
}
useEffect(() => {
const mouseup = () => {
setPanning(false)
}
const mousemove = (event) => {
if (isPanning) {
setPosition({
...position,
x: position.x + event.clientX - position.oldX,
y: position.y + event.clientY - position.oldY,
oldX: event.clientX,
oldY: event.clientY
})
}
}
window.addEventListener('mouseup', mouseup)
window.addEventListener('mousemove', mousemove)
return () => {
window.removeEventListener('mouseup', mouseup)
window.removeEventListener('mousemove', mousemove)
}
})
return (
<div className='viewBox'>
<div
className="PanAndZoomImage-container"
ref={containerRef}
onMouseDown={onMouseDown}
onWheel={onWheel}
>
<svg baseProfile="tiny" fill="#7c7c7c" height="1982" stroke="#ffffff" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" version="1.2" viewBox="0 0 1000 1982" width="1000" xmlns="http://www.w3.org/2000/svg" onLoad={onLoad}>
<g className='regions' style={{
transform: `translate(${position.x}px, ${position.y}px) scale(${position.z})`,
}}>
If anyone knows a solution, any help would be greatly appreciated!
you can try
<YourComponent
onMouseEnter={(e) => {disableScroll.on()}}
onMouseLeave={(e) => {disableScroll.off()}}
/>
use lib from https://github.com/gilbarbara/disable-scroll

d3 / React / Hooks - Updates do not clean up old rects and Groups

I am working on a realtime updating bar chart implementation using d3 with React and Hooks.
Though the new 'g' groups and rects do get added to the svg, the old groups do not seem to be getting cleared up. So, the rects just get added on top of the old rects, as do the axis groups.
I am using the .join() API so I shouldn't need to do manually clean up with exit.remove() right? I am completely new to d3 so forgive the uncertainty.
app:
import React, { useRef, useEffect, useState } from 'react';
import * as firebase from 'firebase';
import Chart from './Chart';
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
function App() {
const [menu, setMenu] = useState([]);
const db = firebase.firestore();
useEffect(() => {
db.collection('dishes')
.get()
.then((res) => {
let data = [];
for (let doc of res.docs) {
data.push(doc.data());
}
setMenu(data);
});
}, []);
useInterval(() => {
let newMenu = [];
newMenu = [...menu];
if (newMenu[0] && newMenu[0].hasOwnProperty('orders')) {
newMenu[0].orders += 50;
setMenu(newMenu);
}
}, 3000);
return (
<div className="App">{menu.length > 0 ? <Chart data={menu} /> : null}</div>
);
}
export default App;
Chart component:
import React, { useRef, useEffect } from 'react';
import * as d3 from 'd3';
const Chart = ({ data }) => {
const height = 600;
// Generate a ref instance
const svgRef = useRef();
useEffect(() => {
const svg = d3.select(svgRef.current);
const margin = { top: 20, right: 20, bottom: 100, left: 100 };
const graphWidth = 600 - margin.left - margin.right;
const graphHeight = height - margin.top - margin.bottom;
// Find the maximum order value
const max = d3.max(data, (d) => d.orders);
// Establish the y scale
// i.e., map my max value to the pixel max value ratio
const y = d3
.scaleLinear()
.domain([0, max * 1.25])
.range([graphHeight, 0]);
// Calculates width and coordinates for each bar
// Can add padding here
const x = d3
.scaleBand()
.domain(data.map((item) => item.name))
.range([0, graphHeight])
.padding(0.25);
const graph = svg
.append('g')
.attr('width', graphWidth)
.attr('height', graphWidth)
.attr('transform', `translate(${margin.left}, ${margin.top})`);
// Creat axis groups for legends and labels
const xAxisGroup = graph
.append('g')
.attr('transform', `translate(0,${graphHeight})`);
const yAxisGroup = graph.append('g');
// Append the graph to the DOM
graph
.selectAll('rect')
.data(data, (entry, i) => entry)
.join(
(enter) => enter.append('rect'),
(update) => update.append('class', 'new'),
(exit) => exit.remove()
)
.transition()
.duration(300)
.attr('width', x.bandwidth)
.attr('height', (d) => graphHeight - y(d.orders))
.attr('fill', 'orange')
.attr('x', (d) => x(d.name))
.attr('y', (d) => y(d.orders));
// Create the axes
const xAxis = d3.axisBottom(x);
const yAxis = d3
.axisLeft(y)
// .ticks(3)
.tickFormat((d) => d + ' orders');
// Append the axes to the graph
xAxisGroup.call(xAxis);
yAxisGroup.call(yAxis);
xAxisGroup
.selectAll('text')
.attr('transform', 'rotate(-40)')
.attr('text-anchor', 'end');
}, [data]);
return (
<div>
<svg ref={svgRef} height={height} width="600" />
</div>
);
};
export default Chart;

Can't access my state in my requestAnimationFrame function

I'm trying to build a space invader game from scratch using React/React-Hook & HTML5 canvas.
So far i achieved to draw my ship on the canvas but i can't figure out how to access my states in the "requestAnimationFrame" function. I did succeed to access REFs but i don't want all my vars to be refs.
So far my code looks like this :
import React from 'react';
import {makeStyles} from '#material-ui/core/styles';
const spaceInvaderStyles = makeStyles((theme) => ({
canvas: {
display: 'block',
margin: 'auto',
imageRendering: 'optimizeSpeed',
imageRendering: '-moz-crisp-edges',
imageRendering: '-webkit-optimize-contrast',
imageRendering: 'optimize-contrast',
backgroundColor: 'black',
}
}))
// GAME CONSTANTS
const GAME = {
shape: {
w:'640px',
h:'640px',
},
shipRow: '600',
shipColor: 'rgba(0,252,0)',
spritesSrc: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAADuCAYAAABh/7RrAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAM4SURBVHhe7ZhRctswDAWrHCH3P6OPkNTuqB0FwxEAEqTkvt0feyYUJCRvX5xsvwr4frK/7WJ7sr9dxsf+KgeLqyG7eLNURstqNhVlSNTV0HX87j5HyXpP1NXA8QyeT97M0etbeDMtRF0NHH93cDwIi6uR8uIvXi94vo1eXwFRVwPHz8g6Oft8BURdDV3HPb9ezHAsw4xnJOpq6C5+tb8V9OzQtXSkbEZY8cPAcTVYXI1QicwuM48ZZUfU1cDxI1c77VHhPFFXQ9fxHp+tY6OdUDHPzvAg6mrg+LuD40FYXI2mF5731qfRnsjOs+d7IOpq6Doe8bPCqRFmPCNRV0PX8f31FM+x0Q6YPb8FUVeDxdXoKg2vjEaZUWYWoq4Gi6tR8sltNnxyK4TF1Wi6c7XTHhXOE3U1dB3v8dk6NtoJFfPsDA+irgaOvzs4HoTF1ej6zGt7IeuXpXpeBKKuBo4fsc5ZrIPeeY/sPHu+B6Kuhq7jET+rHcxe751/4d3TQtTV0F0868Yd6dmhZOlI+ZxxxTcfx9VgcTWapTJaVrOpKEOiroau43f3OUrWe6KuBo5n8HzyZo5e38KbaSHqauD4u4PjQVhcjZAX2R7wfKue1wNRVwPHj3gOjjo8+/oIRF0NHD9jtnMrnLYQdTVYXI2ucptRNkdW3I+oq8HiajT/y+qVSXX5ZOfZ8y+yz0DU1cDxI9aX1pmVRJ7HnvEg6mrgeIaIcxkq5tkZHkRdDRx/d3A8CIur0fTi7t5nfW5B1NXA8TOs8xWOnbHifkRdDRw/stppjxnPQ9TVYHE1QiWxuuxW3I+oq8HianSVRnX5ZOc9j3/ub//xvOSxvw1B1NXA8TOsg6sZ7ZAWRF0NHD/iOW2dG+2A7Dx7vgeiroau46N+3oWs90RdDRw/Yn25ugciz2PPeBB1NXD8iOdU1qcs3v3s119kn4moq6Hr+P6aouXYEc+30esrIOpqsLga00ukxcf3549y+9oey5+DqKvB4mosKRVbZh4ryo6oq8HiarC4GrKLN39fZn/v3p3W5wKiroau4/+bz1GIuhosrgaLq8HiarC4GiyuBourIbv4n/9Fqf1N/rU9tt/3UY/ZXED3UQAAAABJRU5ErkJggg==',
sprites: {
ship: {
x: 0,
y: 204,
w: 61,
h: 237,
}
},
player: {
initialPos: {
x: 290,
y: 580,
}
}
}
const KEYS = {
left:81,
right:68,
down: 83,
up: 90,
arrowLeft: 37,
arrowRight: 39,
arrowDown: 40,
arrowUp: 38,
}
const SpaceInvader = (props) => {
const classes = spaceInvaderStyles();
const canvasRef = React.useRef();
const [cctx, setCctx] = React.useState(null);
const [sprites, setSprites] = React.useState(null);
const [player, setPlayer] = React.useState({
pos: GAME.player.initialPos.x,
})
// keys
const [currentKey, setCurrentKey] = React.useState(null);
//time
let lastTime = 0;
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
// INIT
// context
const canvas = canvasRef.current;
const context = canvas.getContext('2d');
setCctx(context)
// sprites
loadSprites();
}, [])
// key handler
React.useEffect(() => {
window.addEventListener('keydown', (event) => handleUserKeyPress(event,true));
window.addEventListener('keyup', (event) => handleUserKeyPress(event,false))
return () => {
window.removeEventListener('keydown', (event) => handleUserKeyPress(event,true));
window.removeEventListener('keyup', (event) => handleUserKeyPress(event,false))
};
}, [cctx, sprites, player, currentKey])
React.useEffect(() => {
if(!cctx) return
animate();
}, [cctx])
React.useEffect(() => {
if(spritesAreLoaded()){
cctx.drawImage(sprites, GAME.sprites.ship.x, GAME.sprites.ship.y, GAME.sprites.ship.w, GAME.sprites.ship.h, GAME.player.initialPos.x, GAME.player.initialPos.y , GAME.sprites.ship.w, GAME.sprites.ship.h)
}
}, [sprites])
React.useEffect(() => {
console.log(counter)
}, [counter])
// utils
const clearCanvas = () => {
cctx.clearRect(0,0, 640, 640);
}
const saveCanvas = () => {
cctx.save();
}
const drawImage = (image, sx, sy, sw, dx, dy, sh, dw, dh) => {
cctx.drawImage(image, sx, sy, sw, dx, dy, sh, dw, dh);
}
const restore = () => {
cctx.restore();
}
const loadSprites = () => {
var spritesImg = new Image();
spritesImg.src = GAME.spritesSrc;
spritesImg.onload = function() {
// sprites are loaded at this point
setSprites(spritesImg);
}
}
const spritesAreLoaded = () => {
return sprites !== null;
}
const move = (direction) => {
// cctx, sprites and all others state vars are at default value here too
clearCanvas();
saveCanvas();
drawImage(sprites, GAME.sprites.ship.x, GAME.sprites.ship.y, GAME.sprites.ship.w, GAME.sprites.ship.h, player.pos + (10 * direction), GAME.player.initialPos.y , GAME.sprites.ship.w, GAME.sprites.ship.h);
restore();
setPlayer({...player, pos: player.pos + (10 * direction)});
}
const handleUserKeyPress = React.useCallback( (event, isDown) => {
event.preventDefault();
const {key, keyCode} = event;
setCurrentKey(isDown ? keyCode : null);
}, [cctx, sprites, player, currentKey])
const updatePlayer = () => {
// currentKey is at default value here...
const direction = currentKey === KEYS.left ? -1 : currentKey === KEYS.right ? 1 : null;
if(direction !== null) move(direction)
}
const animate = (time) => {
var now = window.performance.now();
var dt = now - lastTime;
if(dt > 100) {
lastTime = now;
updatePlayer();
};
requestAnimationFrame(animate);
}
return (
<canvas
className={classes.canvas}
ref={canvasRef}
width={GAME.shape.w}
height={GAME.shape.h}
/>
)
}
export default SpaceInvader;
I'm trying to access "currentKey" in the thread function but it always return "null" (the default state value),
I found on some topic that you need to bind a context to the animate function but i don't know how to do it with a functional component (with a class component i would do a .bind(this))
I'm pretty new at HTML5 canvas so I might not be able to see the problem here.
All tips are appreciated,
Thanks in advance !
Finally after a lot of testing I did that :
React.useEffect(() => {
console.log("use Effect");
if(!cctx) return
requestRef.current = requestAnimationFrame(animate);
return () => cancelAnimationFrame(requestRef.current);
})
and it works...
If anyone have a cleaner solution
EDIT 1 : After some more testing, it might not be a viable solution, it causes too many re-render

Resources