I am trying to reference a canvas DOM element to make the width of it the same as its parent without messing with the scale of the canvas. When doing so I am getting a Uncaught TypeError: _canvasRef$current.getContext is not a function error and am not exactly sure how to fix it.
Ive console logged a few things just to make the app run but the issue is within the useEffect
import SignaturePad from 'react-signature-canvas'
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const ctx: CanvasRenderingContext2D | null | undefined =
canvasRef.current?.getContext('2d')
const ctxContainer = document.getElementById('ctxContainer')
console.log('ctx: ', ctx)
// if (ctx && ctxContainer) {
// ctx.style.width = '100%'
// ctx.style.height = '100%'
// ctx.width = ctxContainer.clientWidth
// }
}, [])
const save = () => {
console.log('saved!')
}
console.log('canvasRef: ', canvasRef)
return (
<div id={`ctxContainer`} className={'signatureInput__option-draw'}>
<SignaturePad
ref={canvasRef}
canvasProps={{
className: 'signatureInput__option-draw__canvas',
}}
onEnd={save}
/>
</div>
)
Try this.
Note that you may still need to do some changes to following code to full fill your full requirement ( such as event handling when the user change the screen sizes. ) The following code will fix the issue you are facing right now.
useEffect(() => {
if (canvasRef.current) {
const canvas = canvasRef.current.getCanvas();
const ctx = canvas.getContext('2d');
// do something with the canvas ref
if (ctx) {
ctx.canvas.width = window.innerHeight;
ctx.canvas.height = window.innerHeight;
}
}
}, []);
Try using getCanvas() . i dont think getContext() is provided with the package
function App() {
const canvasRef = React.createRef(null);
useEffect(() => {
const canvas = canvasRef.current;
console.log(canvas.current);
const ctx = canvas.getCanvas();
const ctxContainer = document.getElementById("ctxContainer");
console.log("ctx: ", ctx);
if (ctx && ctxContainer) {
ctx.style.width = "100%";
ctx.style.height = "100%";
ctx.width = ctxContainer.clientWidth;
}
}, []);
const save = () => {
console.log("saved!");
};
console.log("canvasRef: ", canvasRef);
return (
<div className="App">
<div id={`ctxContainer`} className={"signatureInput__option-draw"}>
<SignaturePad
ref={(ref) => {
canvasRef.current = ref;
}}
canvasProps={{
className: "signatureInput__option-draw__canvas",
}}
onEnd={save}
/>
</div>
</div>
);
}
Related
I got stuck while coding a webcam app with reactjs
The reason I am using react-webcam only for the input and a separate canvas element as output is because I need to draw something on top of the output.
But the only thing I see is a black screen.. Nothing more, and I don't get it why. My console.log gives me some "positive" output, means that it logs video.readyState=4 and it shows me a video element. But it only renders a black screen.. Hope someone can give me a hint.
I got inspired by this article: https://medium.com/#pdx.lucasm/canvas-with-react-js-32e133c05258
and: Draw rectangle over HTML 5 tag video
Here is the relevant file:
import React, { useEffect, useRef, useState } from "react";
import { render } from "react-dom";
import Webcam from "react-webcam";
const VIDEO_CONSTRAINTS = {
width: 640,
height: 480,
facingMode: "user",
deviceId: "",
frameRate: { max: 1, ideal: 1 },
};
const Stream = () => {
const webcamRef = useRef() as React.MutableRefObject<Webcam>;
const canvasRef = useRef() as React.MutableRefObject<HTMLCanvasElement>;
const [mediaStreamReady, setMediaStreamReady] = useState(false);
const draw = (ctx: CanvasRenderingContext2D, video: HTMLVideoElement) => {
ctx.fillRect(0, 0, VIDEO_CONSTRAINTS.width, VIDEO_CONSTRAINTS.height);
ctx.translate(VIDEO_CONSTRAINTS.width, 0);
ctx.scale(-1, 1);
ctx.clip();
console.log(video);
ctx.drawImage(
video,
0,
0,
VIDEO_CONSTRAINTS.width,
VIDEO_CONSTRAINTS.height,
);
};
const onUserMediaError = () => {
console.log("ERROR in Camera!");
};
const onUserMedia = () => {
console.log("onUserMedia: Camera loaded!");
setMediaStreamReady(true);
};
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
canvas.width = VIDEO_CONSTRAINTS.width;
canvas.height = VIDEO_CONSTRAINTS.height;
if (!ctx) return;
if (!mediaStreamReady) return;
const render = () => {
const video = webcamRef.current.video;
if (!video) return;
console.log("render..");
draw(ctx, video);
requestAnimationFrame(render);
};
render();
});
return (
<>
<Webcam
audio={false}
ref={webcamRef}
videoConstraints={VIDEO_CONSTRAINTS}
onUserMediaError={onUserMediaError}
onUserMedia={onUserMedia}
style={{ opacity: 1 }}
width={"200px"} // only to get safe that I receive a stream
height={"100px"} // only to get safe that I receive a stream
/>
<canvas ref={canvasRef} />
</>
);
};
export default Stream;
I'm making a "sticky" header when scrollY meets offsetTop, but I don't want to hard code the offset. I've been attempting to store the initial offsetTop which can then be checked inside the isSticky() method attached to the scroll listener.
So far I've been unable to make the value stick (no pun intended) and it appears to be null inside the method being run.
const ProfileHeader: React.FC<ProfileHeaderData> = ({ profile }) => {
const bannerUrl = getBannerImageUrl(profile.id, null, profile.bannerImageVersion);
const logoUrl = getLogoImageUrl(profile.id, null, profile.logoImageVersion);
const headerRef = useRef<HTMLDivElement>(null);
const [sticky, setSticky] = useState("");
const [headerOffSet, setHeaderOffSet] = useState<number>(null);
console.log(`Outside: ${headerOffSet}`);
// on render, set listener
useEffect(() => {
console.log(`Render: ${headerRef.current.offsetTop}`);
setHeaderOffSet(headerRef.current.offsetTop);
window.addEventListener("scroll", isSticky);
return () => {
window.removeEventListener("scroll", isSticky);
};
}, []);
const isSticky = () => {
/* Method that will fix header after a specific scrollable */
const scrollTop = window.scrollY;
let originalOffset = headerOffSet ?? headerRef.current.offsetTop;
if (headerOffSet === null) {
console.log(`Setting header off set`);
setHeaderOffSet(originalOffset);
}
console.log(`top: ${scrollTop} | offset: ${originalOffset} | state: ${headerOffSet}`);
const stickyClass = scrollTop >= originalOffset ? styles.sticky : "";
setSticky(stickyClass);
};
return (
<div className={styles.container}>
<div className={styles.bannerContainer}>
{profile.bannerImageVersion && (
<Image
src={bannerUrl}
layout='fill'
className={styles.banner}
/>
)}
</div>
<div ref={headerRef} className={`${styles.header} ${sticky}`}>
<div className={styles.logoContainer}>
{profile.logoImageVersion && (
<Image
src={logoUrl}
layout='fill'
className={styles.logo}
/>
)}
</div>
<FlagCircleIcon {...profile.country} size={32} />
<h1 className={styles.displayName}>{profile.displayName}</h1>
</div>
</div>
)
}
On the initial page load, I get the following console output:
As I start to scroll, the output is as follows:
Seems like the state is never set?
Found a blog post explaining that you need to use a reference (useRef) and access the value via that inside the listener.
https://medium.com/geographit/accessing-react-state-in-event-listeners-with-usestate-and-useref-hooks-8cceee73c559
This references another SO post:
React useState hook event handler using initial state
EDIT
Providing working example based on original question code and solution provided by the blog post. The change the key change is to have a references to the useState variables and read from those inside the event listener.
import { useEffect, useRef, useState } from 'react';
import styles from './testComponent.module.scss';
export const TestComponent: React.FC = ({ children }) => {
const headerRef = useRef<HTMLDivElement>(null);
const [sticky, _setSticky] = useState(false);
const stickyRef = useRef(sticky);
const setSticky = (data: boolean) => {
stickyRef.current = data;
_setSticky(data);
}
const [headerOffSet, _setHeaderOffSet] = useState<number>(null);
const headerOffSetRef = useRef(headerOffSet);
const setHeaderOffSet = (data: number) => {
headerOffSetRef.current = data;
_setHeaderOffSet(data);
}
const isSticky = () => {
const scrollTop = window.scrollY;
const isSticky = scrollTop >= headerOffSetRef.current;
setSticky(isSticky);
};
// on render, set listener
useEffect(() => {
setHeaderOffSet(headerRef.current.offsetTop);
window.addEventListener("scroll", isSticky);
return () => {
window.removeEventListener("scroll", isSticky);
};
}, []);
return (
<div className={styles.container}>
<div ref={headerRef} className={`${styles.header} ${sticky ? styles.isSticky : null}`}>
{children}
</div>
</div>
)
}
I have a react component Button in which I have two states name and players. I want to import these states into another JS file. This file is a vanilla javascript file and not a component.
Here are the codes:
Button.js
import {useState} from "react"
import {buttonContent} from '../buttonContent'
import {correctAnswer} from "../pageTraversal"
import {verifyResults} from "../verifyResults"
const Button = ({textInput, updateApp, updatePage, updateError}) => {
const [pageTitle, updateButton] = useState("home-page")
const [textVisible, textVisibility] = useState("")
const [disabled, changeClick] = useState(false)
const [cursor, changeCursor] = useState("pointer")
const [name, updateName] = useState('')
const [players, updateFriends] = useState('')
const startPages = ["home-page", "welcome", "scenario", "the-adventure-begins"]
const navigatePage = () => {
if (startPages.includes(pageTitle)){
changeCorrectPage()
return
}
if(textInput===""){
updateError("")
return
}
else{
updateError("remove")
}
const response = verifyResults(pageTitle, textInput)
if (pageTitle === "instructions"){
updateName(response["player"])
updateFriends(response["friends"])
changeCorrectPage()
}
if(response === "correct"){
changeCorrectPage()
}
}
const changeCorrectPage = () => {
const page = correctAnswer[pageTitle]
updateApp(page)
updatePage(page)
changeButton(page)
}
const changeButton = (page) => {
textVisibility("hide")
changeClick(true)
changeCursor("auto")
setTimeout(() => {
updateButton(page)
}, 2500)
setTimeout(() => {
textVisibility("show")
}, 4500)
setTimeout(() => {
changeClick(false)
changeCursor("pointer")
}, 6500)
}
return (
<div>
<button className={`button mx-auto`}
id={(pageTitle==="home-page" ? "home-button" : "")}
style={{
"cursor": {cursor},
}}
disabled={disabled}
onClick={navigatePage}
>
<h1 className={`button-text ${textVisible}`}>
{buttonContent[pageTitle]}
</h1>
</button>
<Players name={name} friends={friends} />
</div>
)
}
export default Button
I want to import the two states name and players into another javascript file below where I want to use them as normal variables.
const width = window.innerWidth;
const height = window.innerHeight;
let textSize = ''
if(height > width){
textSize = "small"
}
console.log(name)
console.log(players)
I have tried importing the variables as normal variables, but that doesn't work here. Please suggest a suitable way of doing this.
There is no official approach for this link of problem. But I suggest to use browser storages like localStorage to handle this.
So:
1) Save the states on localStorage based on their changes with useEffect()
useEffect(()=>{
localStorage.setItem("name", name);
localStorage.setItem("players", players);
},[name,players])
2) Get the states data with localStorage everywhere that you want
const width = window.innerWidth;
const height = window.innerHeight;
let textSize = ''
if(height > width){
textSize = "small"
}
console.log(localStorage.getItem("name"))
console.log(localStorage.getItem("players"))
I am trying to calculate the width of child component .is it possible to calculate width of children's ? here is my code
here is my code
https://codesandbox.io/s/reverent-hermann-yt7es?file=/src/App.js
<Tabs>
{data.map((i) => (
<li>{i}</li>
))}
</Tabs>
TABS.js
import React, { useEffect, useRef } from "react";
const Tabs = ({ children }) => {
const tabsRef = useRef(null);
const setTabsDimensions = () => {
if (!tabsRef.current) {
return;
}
// initial wrapper width calculation
const blockWidth = tabsRef.current.offsetWidth;
// const showMoreWidth = moreItemRef.current.offsetWidth;
// calculate width and offset for each tab
let tabsTotalWidth = 0;
const tabDimensions = {};
children.forEach((tab, index) => {
if (tab) {
console.log(tab);
// const width = !isMobile ? 200 : 110;
// tabDimensions[index] = {
// width,
// offset: tabsTotalWidth
// };
// tabsTotalWidth += width;
}
});
};
useEffect(() => {
setTabsDimensions();
});
return (
<ul ref={tabsRef} className="rc64nav">
{children}
</ul>
);
};
export default Tabs;
I know using ref we can access the dom property but how to apply ref in children element element.
here i am trying to calculate all item width
children.forEach((tab, index) => {
if (tab) {
console.log(tab);
Just answer your question how to get child's ref.
const Parent = () => {
const childRef = useRef()
// An example of use the childRef
const onClick = () => {
if (!childRef.current) return
console.log(childRef.current.offsetWidth)
}
return <Child childRef={childRef} />
}
const Child = ({ childRef }) => {
return <div ref={childRef}>Hello Child</div>
}
NOTE: the way I pass ref here is exactly the same way you pass parent method to child. And also ref supports two formats, i'm using the simple one, but you can also use () => {} format. https://reactjs.org/docs/refs-and-the-dom.html
I have a parent component that passes its size as a prop and renders a child component containing a canvas. It looks like this :
const CanvasContainer = () => {
const containerRef = useRef(null);
const [canvasWidth, setCanvasWidth] = useState(0);
const [canvasHeight, setCanvasHeight] = useState(0);
useLayoutEffect(() => {
const container = containerRef.current;
if (!container) return;
setCanvasWidth(container.offsetWidth);
setCanvasHeight(container.offsetHeight);
}, []);
return (
<div ref={containerRef}>
<Canvas canvasWidth={canvasWidth} canvasHeight={canvasHeight} />
</div>
);
};
The child component saves a ref to the canvas, creates the canvas context, modifies some properties of the canvas context, and saves this context to another ref.
However, when the context gets saved to the ref, it is saved with its default properties instead of the properties that I had modified.
const Canvas = (props) => {
const canvasRef = useRef(null);
const contextRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const context = canvas.getContext('2d');
if (!context) return;
context.lineCap = 'round';
console.log(contextRef.current); // Logs null
contextRef.current = context;
console.log(contextRef.current); // Logs CanvasRenderingContext2D but lineCap is still set to its default value ('butt' instead of 'round')
}, []);
const { canvasWidth, canvasHeight } = props;
return (
<canvas
ref={canvasRef}
width={canvasWidth}
height={canvasHeight}
/>
);
};
I tried to replace useEffect with useLayoutEffect but it doesn't change anything.
I noticed that this bug is not occurring when I add canvasWidth and canvasHeight to the useEffect dependency array.
Is this behavior expected or is this some kind of bug ?
Live Example:
const {useRef, useState, useEffect, useLayoutEffect} = React;
const CanvasContainer = () => {
const containerRef = useRef(null);
const [canvasWidth, setCanvasWidth] = useState(0);
const [canvasHeight, setCanvasHeight] = useState(0);
useLayoutEffect(() => {
const container = containerRef.current;
if (!container) return;
setCanvasWidth(container.offsetWidth);
setCanvasHeight(container.offsetHeight);
}, []);
return (
<div ref={containerRef}>
<Canvas canvasWidth={canvasWidth} canvasHeight={canvasHeight} />
</div>
);
};
const Canvas = (props) => {
const canvasRef = useRef(null);
const contextRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const context = canvas.getContext('2d');
if (!context) return;
context.lineCap = 'round';
console.log(context.lineCap);
console.log(contextRef.current); // Logs null
contextRef.current = context;
console.log(contextRef.current); // Logs CanvasRenderingContext2D but lineCap is still set to its default value ('butt' instead of 'round')
console.log(contextRef.current.lineCap);
}, []);
const [isDrawing, setIsDrawing] = useState(false);
const startDrawing = (event) => {
const { offsetX, offsetY } = event.nativeEvent;
if (!contextRef.current) return
console.log(contextRef.current.lineCap); // Logs 'butt' instead of 'round'
contextRef.current.beginPath();
contextRef.current.moveTo(offsetX, offsetY);
setIsDrawing(true);
};
const draw = (event) => {
if (!isDrawing) return;
const { offsetX, offsetY } = event.nativeEvent;
if (!contextRef.current) return
contextRef.current.lineTo(offsetX, offsetY);
contextRef.current.stroke();
};
const stopDrawing = () => {
if (!contextRef.current) return
contextRef.current.closePath();
setIsDrawing(false);
};
const { canvasWidth, canvasHeight } = props;
return (
<canvas
ref={canvasRef}
width={canvasWidth}
height={canvasHeight}
// Event Handlers
onMouseDown={startDrawing}
onMouseMove={draw}
onMouseUp={stopDrawing}
/>
);
};
ReactDOM.render(<CanvasContainer />, document.getElementById("root"));
#root > div {
height: 200px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>