react-spring and react-intersection-observer - tons of rerenders - reactjs

JSFiddle
Code:
export default function App() {
const spring = useSpring({ from: { opacity: 0 }, to: { opacity: 1 } });
const [ref] = useInView();
rerenders++;
return (
<div style={{ height: "200vh" }}>
<div style={{ height: "150vh" }}></div>
<animated.div
ref={ref}
style={{
height: "50px",
width: "50px",
backgroundColor: "red",
opacity: spring.opacity
}}
>
Hello!
</animated.div>
</div>
);
}
Attaching useInView's ref (a hook from react-intersection-observer) causes constant rerendering of the component. Why is that so?
Using an IntersectionObserver yourself does not do such a thing:
const ref = useRef<any>();
useLayoutEffect(() => {
const obs = new IntersectionObserver((entries, observer) => {
entries.forEach((entry, index) => {
console.log(entry);
});
});
obs.observe(ref.current);
}, []);

Related

The react-use-cart needs the string ( id ) but my api data only generates the object ( _id ), how can I change it and use it normally?

I'm creating a cart and I need to add the item, the function has the id of the item as a requirement, but my item only generates the _id and not just the id. The problem to be simple for those who have a lot of knowledge, thanks in advance to the collaborators
https://www.npmjs.com/package/react-use-cart
Index.js ( Product details )
import React, { useState, useEffect } from "react";
import { useParams } from "react-router";
import Popup from "reactjs-popup";
import "reactjs-popup/dist/index.css";
import { ThreeDots } from "react-loader-spinner";
import { useCart } from "react-use-cart";
//Local
import Nav from "../../components/Nav";
import api from "../../../services/api";
import Best from "../Best/index";
import { TitleCaurosel } from "../style";
//Icons / Images
import { AiFillStar } from "react-icons/ai";
import { BsArrowLeft, BsArrowRight } from "react-icons/bs";
//Style
import {
Product,
Images,
Main,
Other,
Details,
Title,
Description,
Stars,
Colors,
Color,
ColorSelected,
Price,
Cart,
} from "./style";
const Index = () => {
const params = useParams();
const { addItem } = useCart();
const [data, setData] = useState(undefined);
const [error, setError] = useState(undefined);
const [color, setColor] = useState(undefined);
const [nameColor, setNameColor] = useState(undefined);
const [index, setIndex] = useState(0);
const [popup, setPopup] = useState(false);
const [current, setCurrent] = useState(0);
const [main, setMain] = useState(undefined);
const ref = React.createRef();
const { height, width } = useWindowDimensions();
function getWindowDimensions() {
const { innerWidth: width, innerHeight: height } = window;
return {
width,
height,
};
}
function useWindowDimensions() {
const [windowDimensions, setWindowDimensions] = useState(
getWindowDimensions()
);
useEffect(() => {
function handleResize() {
setWindowDimensions(getWindowDimensions());
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return windowDimensions;
}
const config = {
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
};
useEffect(() => {
api.get(`/items/${params.id}`).then((response) => setData(response.data));
}, []);
const addToCart = () => {
if (color !== undefined) {
api
.post(
`/cart`,
{
itemId: params.id,
quantity: 1,
colors: data.images,
colorSelected: color,
img: data.images[0].src,
},
config
)
.then((response) => {
if (response.status === 200 || response.status === 201) {
window.location.pathname = "/Cart";
}
});
} else {
setError(1);
}
};
const numberFormat = (value) =>
new Intl.NumberFormat("pt-br", {
style: "currency",
currency: "BRL",
}).format(value);
const handleTab = (index) => {
setMain(undefined);
setIndex(index);
if (color !== undefined && nameColor !== undefined) {
setColor(undefined);
setNameColor(undefined);
}
};
const nextSlide = () => {
setCurrent(current === data.images.length - 1 ? 0 : current + 1);
};
const prevSlide = () => {
setCurrent(current === 0 ? data.images.length - 1 : current - 1);
};
useEffect(() => {
if (data !== undefined) {
if (!Array.isArray(data.images) || data.images.length <= 0) {
return null;
}
}
});
const changeColor = (id, name) => {
setColor(id);
setNameColor(name);
setError(undefined);
const filter = data.images.filter((item) => item._id === id);
setMain(filter[0].src);
};
return data !== undefined ? (
<div>
{/*Popup*/}
{popup === true && (
<Popup
open={true}
position="center center"
onClose={() => setPopup(false)}
>
<section className="slider">
<BsArrowLeft className="left-arrow" onClick={prevSlide} />
<BsArrowRight className="right-arrow" onClick={nextSlide} />
{data.images.map((slide, index) => {
return (
<div
className={index === current ? "slide active" : "slide"}
key={index}
>
{index === current && (
<div
style={{
width: width / 2,
height: height / 2,
backgroundImage: `url(${slide.src})`,
backgroundRepeat: "no-repeat",
backgroundSize: "contain",
backgroundPosition: "center",
}}
/>
)}
</div>
);
})}
</section>
</Popup>
)}
<Nav />
<div
style={{
height: "90vh",
display: "grid",
justifyContent: "center",
alignItems: "center",
gridTemplateColumns: "85%",
gridTemplateRows: "100%",
gridColumnGap: "5px",
}}
>
<Product key={data._id}>
<Images>
<div
style={{
display: "grid",
marginRight: "3%",
height: height / 1.3,
}}
ref={ref}
>
<Other
style={{
width: width / 10,
backgroundImage: `url(${data.images[0].src})`,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "60%",
}}
alt={`Image de ${data.name}`}
key={0}
onClick={() => handleTab(0)}
/>
{data.images[1] !== undefined && (
<Other
style={{
marginTop: "5%",
width: width / 10,
backgroundImage: `url(${data.images[1].src})`,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "60%",
}}
alt={`Image de ${data.images[1].name}`}
key={1}
onClick={() => handleTab(1)}
/>
)}
{data.images[2] !== undefined && (
<Other
style={{
marginTop: "5%",
width: width / 10,
backgroundImage: `url(${data.images[2].src})`,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "60%",
}}
alt={`Image de ${data.name}`}
key={2}
onClick={() => handleTab(2)}
/>
)}
{data.images[3] !== undefined && (
<Other
style={{
marginTop: "5%",
width: width / 10,
backgroundImage: `url(${data.images[3].src})`,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "60%",
}}
alt={`Image de ${data.name}`}
key={1}
onClick={() => handleTab(3)}
/>
)}
{data.images[4] !== undefined && (
<div>
{data.images.length < 5 ? (
<Other
style={{
width: width / 10,
backgroundImage: `url(${data.images[3].src})`,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "60%",
}}
alt={`Image de ${data.name}`}
key={2}
onClick={() => handleTab(3)}
/>
) : (
<Other
style={{
width: width / 10,
}}
onClick={() => setPopup(true)}
alt={`Image de ${data.name}`}
>
<p>+ 5</p>
</Other>
)}
</div>
)}
</div>
<div
style={{
backgroundColor: "#F6F6F6",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Main
style={{
backgroundImage: `url(${
main === undefined ? data.images[index].src : main
})`,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "80%",
width: width / 2.8,
height: height / 1.5,
}}
/>
</div>
</Images>
<Details>
<Title>{data.name}</Title>
<Description>{data.description}</Description>
<Stars>
<AiFillStar />
<AiFillStar />
<AiFillStar />
<AiFillStar />
<AiFillStar />
<p>441 Avaliações</p>
</Stars>
<Colors>
{error === undefined && color === undefined && (
<p style={{ color: "#000" }}>Selecione uma cor:</p>
)}
{error === 1 && color === undefined && (
<p style={{ color: "#ff0000" }}>
Selecione uma cor: (Obrigatório)
</p>
)}
{error === undefined &&
color !== undefined &&
nameColor !== undefined && (
<p style={{ color: "#000" }}>Cor selecionada: {nameColor}</p>
)}
<div
style={{
display: "flex",
marginTop: "1%",
width: "100%",
flexWrap: "wrap",
alignItems: "center",
}}
>
{data.images.map((item) =>
color === item._id ? (
<ColorSelected onClick={() => setColor(undefined)}>
<div style={{ backgroundColor: item.color }} />
</ColorSelected>
) : (
<Color
style={{ backgroundColor: item.color }}
onClick={() => {
changeColor(item._id, item.name);
}}
/>
)
)}
</div>
</Colors>
<Price>{numberFormat(data.price)}</Price>
{console.log(data)}
<Cart onClick={() => addItem(data)}>
Adicionar ao carrinho
</Cart>
</Details>
</Product>
</div>
<div style={{ paddingLeft: "130px" }}>
<TitleCaurosel>Você pode gostar!</TitleCaurosel>
<Best />
</div>
</div>
) : (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: width,
height: height,
}}
>
<ThreeDots
height="300"
width="300"
radius="9"
color="#8c6d55"
ariaLabel="three-dots-loading"
wrapperStyle={{}}
wrapperClassName=""
visible={true}
/>
</div>
);
};
export default Index;
Cart.js
import React, { useState, useEffect } from "react";
import Nav from "../components/Nav";
import Select from "react-select";
import api from "../../services/api";
import { ThreeDots } from "react-loader-spinner";
import { useCart } from "react-use-cart";
//Icons
import { BiTrash } from "react-icons/bi";
import { MdOutlineEditLocationAlt } from "react-icons/md";
import { HiOutlineShoppingBag } from "react-icons/hi";
//Style
import {
Container,
Box1,
Box2,
Box3,
Title,
Product,
Image,
Name,
Price,
Border,
Stock,
PriceTotal,
Delete,
Cupom,
BorderTraced,
Subtotal,
Items,
Total,
Checkout,
NextToBuy,
Empty,
} from "./style";
const Index = () => {
function getWindowDimensions() {
const { innerWidth: width, innerHeight: height } = window;
return {
width,
height,
};
}
function useWindowDimensions() {
const [windowDimensions, setWindowDimensions] = useState(
getWindowDimensions()
);
useEffect(() => {
function handleResize() {
setWindowDimensions(getWindowDimensions());
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return windowDimensions;
}
const { height, width } = useWindowDimensions();
const {
isEmpty,
items,
cartTotal,
updateItemQuantity,
removeItem,
emptyCart,
} = useCart();
return (
<Container>
{isEmpty ? (
<Empty>
<Nav />
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
textAlign: "center",
height: "80vh",
}}
>
<div>
<HiOutlineShoppingBag className="icon-empty" />
<h1>Sua sacola está vazia</h1>
<p className="text-empty">
Parece que você não adicionou nada a sua sacola. <br /> Vá em
frente e explore as principais categorias.
</p>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
textAlign: "center",
}}
>
<a className="button" href="/#Produtos">
Explorar produtos
</a>
</div>
</div>
</div>
</Empty>
) : (
<Container>
<Nav />
<div
style={{
display: "flex",
justifyContent: "center",
height: height,
marginTop: "3%",
marginBottom: "10%",
}}
>
<Box1 style={{ width: width / 2.8, height: height }}>
<Title>Seus items</Title>
</Box1>
<div>
<Box2 style={{ width: width / 4, height: height / 5 }}>
<Title>Frete</Title>
<p className="title">Endereço:</p>
<span></span>
<span></span>
<p className="title">Estimativa de entrega:</p>
<span>30/12/2022</span>
<MdOutlineEditLocationAlt />
</Box2>
<Box3 style={{ width: width / 4, height: height / 1.4 }}>
<Cupom>
<input placeholder="Cupom de desconto" />
<button>Aplicar</button>
</Cupom>
<BorderTraced />
<Subtotal>
<p>Subtotal</p>
<p></p>
</Subtotal>
<Items>
<div>
<p>Frete</p>
<p>R$ 399,99</p>
</div>
</Items>
<BorderTraced />
<Total>
<p>Total</p>
<p></p>
</Total>
<Checkout>Finalizar compra</Checkout>
<NextToBuy
onClick={() => {
window.location.pathname = "/Products";
}}
>
Continuar comprando
</NextToBuy>
</Box3>
</div>
</div>
</Container>
)}
</Container>
);
};
export default Index;
Index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import Routes from './routes';
import { CartProvider } from 'react-use-cart';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<CartProvider>
<Routes />
</CartProvider>
</React.StrictMode>
);
Not sure if this is the best/most efficient approach, but you could always create an Item object with the attributes required for the addItem() method. Then map the required data from your data object to the new item object, and instead of using addItem(data) you could use addItem(item).
Add the property when setting the value:
useEffect(() => {
api.get(`/items/${params.id}`)
.then(({ data }) => {
setData({
...data,
id: data._id
});
});
}, []);

Sync scroll react. div block with main scroll on window

I want to synchronize a divs scroll with a body scroll.
I tried some examples with two divs but I couldn't manage fix it with the body scroll.
Sample code with two divs: https://codesandbox.io/s/react-custom-scroll-sync-of-2-divs-10xpi
My Code
https://codesandbox.io/s/funny-rain-ditbv
import "./styles.css";
import { useRef } from "react";
export default function App() {
const firstDivRef = useRef();
const secondDivRef = useRef();
const handleScrollFirst = (scroll) => {
secondDivRef.current.scrollTop = scroll.target.scrollTop;
};
const handleScrollSecond = (scroll) => {
firstDivRef.current.scrollTop = scroll.target.scrollTop;
};
return (
<div
className="App"
style={{
display: "flex",
}}
>
<div
onScroll={handleScrollFirst}
ref={firstDivRef}
style={{
height: "500px",
overflow: "scroll",
backgroundColor: "#FFDAB9",
position: "sticky",
top: "0px"
}}
>
<div style={{ height: 5000, width: 300 }}>
The first div (or it can be tbody of a table and etc.)
{[...new Array(1000)].map((_, index) => {
const isEven = index % 2 === 0;
return (
<div style={{ backgroundColor: isEven ? "#FFFFE0 " : "#FFDAB9" }}>
{index}
</div>
);
})}
</div>
</div>
<div
onScroll={handleScrollSecond}
ref={secondDivRef}
style={{
height: "100%",
backgroundColor: "#EEE8AA"
}}
>
<div style={{ height: 5000, width: 200 }}>
The second div
{[...new Array(1000)].map((_, index) => {
const isEven = index % 2 === 0;
return (
<div style={{ backgroundColor: isEven ? "#FFFFE0 " : "#FFDAB9" }}>
{index}
</div>
);
})}
</div>
</div>
</div>
);
}
It was easy to use different divs rather than using a div and window.
But finally managed to run it with a div and the body.
The trick is they block each other since they listen each others values.
import "./styles.css";
import { useEffect, useRef, useState } from "react";
export default function App() {
const firstDivRef = useRef();
const [scrollTop, setScrollTop] = useState(0);
const [disableBodyScroll, setDisableBodyScroll] = useState(false);
const handleScrollFirst = (scroll) => {
setScrollTop(scroll.target.scrollTop);
};
useEffect(() => {
if (firstDivRef.current && !disableBodyScroll) {
firstDivRef.current.scrollTop = scrollTop;
}
if (disableBodyScroll) {
window.scrollTo(0, scrollTop);
}
}, [firstDivRef, scrollTop, disableBodyScroll]);
useEffect(() => {
const onScroll = () => {
console.log(disableBodyScroll, window.scrollY);
if (!disableBodyScroll) {
setScrollTop(window.scrollY);
}
};
// clean up code
window.removeEventListener("scroll", onScroll);
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, [disableBodyScroll]);
return (
<div
className="App"
style={{
display: "flex"
}}
>
<div
onMouseEnter={() => setDisableBodyScroll(true)}
onMouseLeave={() => setDisableBodyScroll(false)}
onScroll={handleScrollFirst}
ref={firstDivRef}
style={{
height: "500px",
overflow: "scroll",
backgroundColor: "#FFDAB9",
position: "sticky",
top: "0px"
}}
>
<div style={{ height: 5000, width: 300 }}>
The first div (or it can be tbody of a table and etc.)
{[...new Array(1000)].map((_, index) => {
const isEven = index % 2 === 0;
return (
<div style={{ backgroundColor: isEven ? "#FFFFE0 " : "#FFDAB9" }}>
{index}
</div>
);
})}
</div>
</div>
<div
style={{
height: "100%",
backgroundColor: "#EEE8AA"
}}
>
<div style={{ height: 5000, width: 200 }}>
The second div
{[...new Array(1000)].map((_, index) => {
const isEven = index % 2 === 0;
return (
<div style={{ backgroundColor: isEven ? "#FFFFE0 " : "#FFDAB9" }}>
{index}
</div>
);
})}
</div>
</div>
</div>
);
}
https://codesandbox.io/s/ancient-dream-tzuel?file=/src/App.js
Try the next example. This is a quick sketch but maybe it will help you.
https://codesandbox.io/s/gallant-goldwasser-19g4d?file=/src/App.js

react setState not rendering everytime button is pressed

I'm trying to use setState to access my css const mystyle object to change the background color on the squares from blue to red but everytime the button is pressed. It seems everytime I press the button the Setstate does not render on screen any advice or help? Would be greatly appreciated
class MyHeader extends React.Component {
constructor(props){
super(props)
this.state = {backgroundColor: 'blue'};
}
render() {
const mystyle = {
borderRadius: "10px",
background: this.state.backgroundColor,
padding: "10px",
width: "100px",
height: "100px",
marginTop: "10px",
lineHeight: "80px",
color: "dimGrey",
fontWeight: "bold",
fontSize: "3em",
textAlign: "center"
};
function State() {
this.setState({backgroundColor: 'red'})
}
return (
<div>
<h1 style={mystyle}></h1>
<h1>{this.state.backgroundColor}</h1>
</div>
);
}
}
function Test() {
function Test2() {
setchange(change + Math.floor(Math.random() * 10));
if(change > 20) {
setchange(change + Math.floor(Math.random() - 10))
}
}
const [change, setchange] = React.useState(1)
return (
<div>
<h1>click the button to randomize colors</h1>
<button onClick={this.State}>Randomize colors!</button>
<div className='.flex-item'></div>
<h1>{change}</h1>
<div className="flex-item"></div>
<MyHeader />
<MyHeader />
</div>
);
}
ReactDOM.render(<Test />, document.getElementById("root"));
my codepen link to the code
The main issue is trying to do something where the child component, MyHeader, has the function to change state, but trying to invoke it from the parent component, Test. It's just simpler to pass the color as a prop from Test to MyHeader.
Alternatively, you can do the useContext thing, but I think this is easier. I've stripped out all superfluous code that wasn't getting used. You can of course, add them back as you need to.
const MyHeader = ({backgroundColor}) => {
const mystyle = {
borderRadius: "10px",
background: backgroundColor,
padding: "10px",
width: "100px",
height: "100px",
marginTop: "10px",
lineHeight: "80px",
color: "dimGrey",
fontWeight: "bold",
fontSize: "3em",
textAlign: "center"
};
return (
<div>
<h1 style={mystyle}></h1>
<h1>{backgroundColor}</h1>
</div>
);
}
}
const Test = (props) => {
const [backgroundColor, setBackgroundColor] = useState("blue");
const onButtonClick = () => {
setBackgroundColor("red");
}
return (
<div>
<h1>click the button to randomize colors</h1>
<button onClick={onButtonClick}>Randomize colors!</button>
<div className='.flex-item'></div>
<h1>{change}</h1>
<div className="flex-item"></div>
<MyHeader backgroundColor={backgroundColor} />
</div>
);
};

react The swipe animation will be performed twice

When you press the Circle button, the Box moves to the right and disappears from the screen.
Additional information (FW/tool version, etc.)
react
scss
Typescript
framer-motion
import "./style.scss";
import React, { FunctionComponent, useState } from "react";
import { useMotionValue, useTransform } from "framer-motion";
import { SwipeBox } from "./SwipeBox";
const App: FunctionComponent = () => {
const [cards, setCards] = useState([
const onClic = () => {
animateCardSwipe({ x: -1400, y: 0 });
}; <div
style={{
width: "400px",
height: "300px",
background: `${card.background}`
}}
>
) : (
</div>
);
};
export default App;
The problem is that the animation is happening on mount and you're updating state twice inside the animateCardSwipe function:
const animateCardSwipe = (animation: { x: number, y: number }) => {
setAnime({ ...anime, animation });
setTimeout(() => {
x.set(0);
y.set(0);
setCards([...cards.slice(0, cards.length - 1)]);
}, 200);
};
I personally like to use a more imperative approach here for starting the animation using animate:
const animateCardSwipe = (animation: { x: number, y: number }) => {
animate(x, animation.x, {
duration: 1,
onComplete: () => {
x.set(0);
y.set(0);
setCards([...cards.slice(0, cards.length - 1)]);
},
});
};
This implementation also waits for the animation to complete before rearranging the cards.
Full example:
import React, { FunctionComponent, useState } from "react";
import {
animate,
useAnimation,
useMotionValue,
useTransform,
MotionValue,
motion,
} from "framer-motion";
interface Props {
animate?: { x: number; y: number };
style: {
x?: MotionValue;
y?: MotionValue;
zIndex: number;
rotate?: MotionValue;
};
card: { text: string; background: string };
}
const SwipeBox: React.FunctionComponent<Props> = (props) => {
return (
<motion.div
animate={props.animate}
className="card"
style={{
...props.style,
background: "white",
borderRadius: "8px",
display: "grid",
top: 0,
left: 0,
placeItems: "center center",
position: "absolute",
width: "100%",
}}
transition={{ duration: 1 }}
>
{props.children}
</motion.div>
);
};
const App: FunctionComponent = () => {
const controls = useAnimation();
console.log(controls);
const [cards, setCards] = useState([
{ text: "Up or down", background: "red" },
{ text: "Left or right", background: "green" },
{ text: "Swipe me!", background: "gray" },
{ text: "Swipe me!", background: "purple" },
{ text: "Swipe me!", background: "yellow" },
]);
const x = useMotionValue(0);
const y = useMotionValue(0);
const animateCardSwipe = (animation: { x: number; y: number }) => {
animate(x, animation.x, {
duration: 1,
onComplete: () => {
x.set(0);
y.set(0);
setCards([...cards.slice(0, cards.length - 1)]);
},
});
};
const [anime, setAnime] = useState<{ animation: { x: number; y: number } }>({
animation: { x: 0, y: 0 },
});
const rotate = useTransform(x, [-950, 950], [-30, 30]);
const onClickLeft = () => {
animateCardSwipe({ x: 1400, y: 0 });
};
const onClickRight = () => {
animateCardSwipe({ x: -1400, y: 0 });
};
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<div>
{cards.map((card, index) =>
index === cards.length - 1 ? (
<SwipeBox
animate={anime.animation}
card={card}
key={index}
style={{ x, y, zIndex: index, rotate: rotate }}
>
<div
style={{
width: "400px",
height: "300px",
background: `${card.background}`,
}}
>
{card.text}
</div>
</SwipeBox>
) : (
<SwipeBox
card={card}
key={index}
style={{
zIndex: index,
}}
>
<div
style={{
width: "400px",
height: "300px",
background: `${card.background}`,
}}
>
{card.text}
</div>
</SwipeBox>
)
)}
</div>
<div style={{ zIndex: 999 }}>
<button onClick={onClickRight}>✖️</button>
<button onClick={onClickLeft}>○</button>
</div>
</div>
);
};
export default App;
Codesandbox Demo

Fit canvas to screen in React

So I have this code base:
import React, { useEffect, useRef, useState } from 'react';
function App() {
const container = useRef(null);
const canvas = useRef(null);
const [ctx, setCtx] = useState(undefined);
useEffect(() => {
setCtx(canvas.current.getContext("2d"));
}, []);
useEffect(() => {
if (!ctx) return;
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, canvas.current.width, canvas.current.height);
}, [ctx]);
return (
<div
style={{
height: "100vh",
display: "flex",
flexDirection: "column",
padding: 8,
position: "relative",
}}
>
<header>
Some Header
</header>
<div style={{ margin: 40, flex: '1 1' }} ref={container}>
<canvas ref={canvas} />
</div>
</div>
);
}
export default App;
It's pretty basic example for a canvas element placed inside a container div.
What I want to do is to resize the canvas width and height according to the user's screen ( and make it responsive ).
So I found out two options:
To use window.addEventListener('resize', ...) or to use ResizeObserver.
I tried them both, but without any success, thats what I tried to do:
import React, { useEffect, useRef, useState } from 'react';
function App() {
const container = useRef(null);
const canvas = useRef(null);
const [ctx, setCtx] = useState(undefined);
const [size, setSize] = useState([0, 0]);
useEffect(() => {
const resize = () => {
const { offsetWidth, offsetHeight } = container.current;
canvas.current.width = offsetWidth;
canvas.current.height = offsetHeight;
setSize([offsetWidth, offsetHeight]);
setCtx(canvas.current.getContext('2d'));
};
resize();
window.addEventListener('resize', resize);
return () => window.removeEventListener('resize', resize);
}, []);
useEffect(() => {
if (!ctx) return;
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, size[0], size[1]);
}, [ctx, size]);
return (
<div
style={{
height: "100vh",
display: "flex",
flexDirection: "column",
padding: 8,
position: "relative",
}}
>
<header>
Some Header
</header>
<div style={{ margin: 40, flex: '1 1' }} ref={container}>
<canvas ref={canvas} style={{ width: size[0], height: size[1] }} />
</div>
</div>
);
}
export default App;
But from some reason it makes the height of the canvas greater in every resize cycle.
Whys that and how can I fix that?
If you don't have a problem using a package for the canvas you can use react-konva which will take care of the responsive nature really well.
So all you have to do is change the height and width as the window is resized and send them as attributes.
...
useEffect(() => {
const checkSize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener("resize", checkSize);
return () => window.removeEventListener("resize", checkSize);
}, []);
...
return (
<Stage
width={size.width}
height={size.height}
>
...
</Stage>
)
...
Demo: codesandbox

Resources