skip to Main Content

For context, I’m trying to make a project using the html canvas in React. I am trying to replace the class components with functional components for uniformity and avoiding mixing of class and functional components. The example below should simply move a blue rectangle from left to right.

I am convinced the file paths are correct, and my canvas and context variables are correctly initialised (code provided a bit later). However I’m a bit lost on how to proceed refactoring the code to make it behave the same as the class component.

This is the actual class and its usage (in vanilla javascript):

class Bar {
  constructor(x, y, width, height, color) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.color = color;
  }
  update(){
    this.x++;
  }
  draw(context){
    context.fillStyle = this.color;
    context.fillRect(this.x, this.y, this.width, this.height);
  }
}

Usage:

const bar1 = new Bar(10, 10, 100, 200, "blue");
function animate(){
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  bar1.draw(ctx);
  requestAnimationFrame(animate);
}
animate();

This is my attempt at mimicking the functionality. First, the Bar functional component:

import React, {useState} from 'react';

const Bar = ({initialX, initialY, initialWidth, initialHeight, initialColor}) => {
    const [x, setX] = useState(initialX);
    const [y, setY] = useState(initialY);
    const [width, setWidth] = useState(initialWidth);
    const [height, setHeight] = useState(initialHeight);
    const [color, setColor] = useState(initialColor);

    const update = () => {
        const newX = x + 1;
        setX(newX);
    }

    const draw = (context) => {
        context.fillStyle = color;
        context.fillRect(x, y, width, height);
    }


    return (
        <div>
            
        </div>
    );
};

export default Bar;

And this is where the Bar component should be used, but I’m somewhat lost on how to "use" it. Do I just use it as a function? Render it as <Bar/> and provide props? Assign <Bar/> to a variable bar and use the draw() function from that?

import Bar from "../components/Bar.jsx";

const Visualiser = () => {
    const canvas = document.getElementById('myCanvas');
    const ctx = canvas.getContext('2d');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    const animate = () => {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        requestAnimationFrame(animate);
    }
    animate();
};

export default Visualiser;

Would appreciate any guidance on how to go about this!

2

Answers


  1. To render to the canvas you need an actual canvas element, which I’m not seeing in your Bar component. In a functional component you can use useRef to create a reference to the canvas, and then start the animation loop in a useEffect.

    const Bar = ({ initialX, initialY, initialWidth, initialHeight, initialColor }) => {
      const [x, setX] = useState(initialX);
      const [y, setY] = useState(initialY);
      const [width, setWidth] = useState(initialWidth);
      const [height, setHeight] = useState(initialHeight);
      const [color, setColor] = useState(initialColor);
    
      const canvasRef = useRef(null); // use this to reference the canvas
    
      const update = () => {
        setX(prevX => prevX + 1);
      };
    
      const draw = (context) => {
        context.clearRect(0, 0, context.canvas.width, context.canvas.height);
        context.fillStyle = color;
        context.fillRect(x, y, width, height);
      };
    
      // This effect will run on first render
      useEffect(() => {
        const canvas = canvasRef.current;
        const context = canvas.getContext('2d');
        
        let animationFrameId;
    
        const render = () => {
          update();
          draw(context);
          // requestAnimationFrame will start the loop and run the render
          // function at the frame rate (usually 60fps)
          animationFrameId = requestAnimationFrame(render);
        };
        
        render();
    
        return () => {
          // This will cancel the loop when this component unmounts
          cancelAnimationFrame(animationFrameId);
        };
      }, []);
    
      return (
        <div>
          <canvas ref={canvasRef} width={500} height={500} />
        </div>
      );
    };
    
    Login or Signup to reply.
  2. If you want to just use it as function you can have it return the draw and update methods.

    return { update: <<<fn here>>>, draw: <<fn here>> }
    

    in parent it would pretty much be the same code you used in class based example.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search