I'm trying to hide some components when the screen hits some specific breakpoint.
My thought process was to store the screen width in a state then when it's below my breakpoint I set the display: none .
The question is how to access the screen width/viewport width in react? and is that the best approach for a responsive design?
Here's a simple example
const useWindowWide = (size) => {
const [width, setWidth] = useState(0)
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth)
}
window.addEventListener("resize", handleResize)
handleResize()
return () => {
window.removeEventListener("resize", handleResize)
}
}, [setWidth])
return useWindowWidth > size
}
and to use it,
const Greeting = () => {
const wide = useWindowWide(600)
return (<h1>{wide ? "Hello World" : "Hello"}</h1>)
}
THere're quite a few hooks in the following reference might help you better.
seWindowSize, https://usehooks.com/useWindowSize/
useWindowSize, https://github.com/jaredLunde/react-hook/tree/master/packages/window-size
you can get width like this
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0)
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0)
But can go alone with CSS to hide certain things in breakpoints and make responsive designs
Only correcting something that went wrong to me in the preview answer. This way worked for me:
const useWindowWide = (size) => {
const [width, setWidth] = useState(0)
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth)
}
window.addEventListener("resize", handleResize)
handleResize()
return () => {
window.removeEventListener("resize", handleResize)
}
}, [setWidth])
return width
}
const wide = useWindowWide(400)
<h1>{wide >= 500 ? "Desktop" : "Mobile"}</h1>
Related
I want to increase the height of this image. How can we change the CSS for that? my code also have below image
You can create a custom component that will handle height and width, and use it.
You can do something like that.
import React, { useEffect, useState } from "react";
import { Image, ImageSourcePropType, ImageStyle, StyleProp } from "react-native";
interface ScaledImageProps {
source: ImageSourcePropType;
width?: number;
height?: number;
style?: StyleProp<ImageStyle> | undefined;
onGetHeight?: (height: number) => void
onGetWidth?: (width: number) => void
}
export const ScaledImage = (props: ScaledImageProps) => {
const [currentWidth, setCurrentWidth] = useState(0);
const [currentHeight, setCurrentHeight] = useState(0);
const setWidth = (width: number) => {
setCurrentWidth(width)
if (props.onGetWidth) props.onGetWidth(width)
}
const setHeight = (height: number) => {
setCurrentHeight(height)
if (props.onGetHeight) props.onGetHeight(height)
}
useEffect(() => {
const uri = Image.resolveAssetSource(props.source).uri
Image.getSize(uri, (width, height) => {
if (props.width && !props.height) {
setWidth(props.width);
setHeight(height * (props.width / width));
} else if (!props.width && props.height) {
setWidth(width * (props.height / height));
setHeight(props.height);
} else {
setWidth(width);
setHeight(height);
}
});
}, []);
return (
<Image
source={props.source}
style={[props.style ,{ height: currentHeight, width: currentWidth, }]}
/>
);
};
and if you want to use it, just call it like the following
<ScaledImage width={100} source={YourImage} />
I am rebuilding this Codepen into React Typescript. I think I am pretty close.
However I am not sure about the event type? For now I added any.
And I get the error Property 'getContext' does not exist on type 'HTMLElement'. I read that it occurs when I try to call the getContext() method on an element that has a type of HTMLElement.
Thats correct in my case. To solve the error, I should use a type assertion to type the element as HTMLCanvasElement before calling getContext. But when I do, it throws an error that the canvas type isnt correct.
Also is the document.removeEventListener and document.addEventListener with touchmove/mousemove a good solution or is there a better alternative in React? I use both so its possible to draw on touchscreens too.
import React from "react";
const styles = {
canvas: {
width: "100vw",
height: "100vh",
cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='40' height='48' viewport='0 0 100 100' style='fill:black;font-size:24px;'><text y='50%'>✍️</text></svg>") 5 25, auto,
}
}
export interface Props {
canvas: HTMLElement;
event: any;
}
function App(props: Props) {
const canvas = document.getElementById("canvas");
const context = props.canvas.getContext("2d");
let coord = { x: 0, y: 0 };
const resize = () => {
context.canvas.width = window.innerWidth;
context.canvas.height = window.innerHeight;
};
const reposition = (props.event) => {
coord.x = props.event.clientX - props.canvas.offsetLeft;
coord.y = props.event.clientY - props.canvas.offsetTop;
};
const start = (event) => {
document.addEventListener("mousemove", draw);
document.addEventListener("touchmove", draw);
reposition(event);
};
const stop = () => {
document.removeEventListener("mousemove", draw);
document.removeEventListener("touchmove", draw);
};
document.addEventListener("mousedown", start);
document.addEventListener("mouseup", stop);
window.addEventListener("resize", resize);
document.addEventListener("touchstart", start);
document.addEventListener("touchend", stop);
resize();
const draw = (event) => {
context.beginPath();
context.lineWidth = 5;
context.lineCap = "round";
context.strokeStyle = "#ACD3ED";
context.moveTo(coord.x, coord.y);
reposition(event);
context.lineTo(coord.x, coord.y);
context.stroke();
};
return <canvas style={styles.canvas}></canvas>;
}
export default App;
There are a number of things that aren't quite right here. The biggest issue seems to be a confusion between what your variables are and where they come from.
Your App component takes a canvas and an event as props, but it also declares a canvas variable locally, and calls event listeners with the event that triggered them rather than the event from props.
The secondary issue is that you need to be using React hooks. Registering and de-registering event listeners is a side-effect which should be done in a useEffect. If you need to modify a top-level variable like coord then it should be in a useState hook. The best way to access the canvas element is via a useRef hook.
The TypeScript issues are a more minor concern, and they're mainly a consequence of the larger variable and structure issues.
Here's all the things that I changed:
Wrapped the entire cursor text in backticks to make it a string.
Deleted Props as your component doesn't need them.
Created a canvasRef and attached it to the <canvas> element. The type of canvasRef.current is HTMLCanvasElement (or null).
Accessed the canvas and context from this ref, avoiding "is possibly null" issues with the optional chaining operator ?. and if statements.
Typed your event arguments as (event: MouseEvent). Note that since these events are from a JavaScript event listener they will be DOM events rather than React synthetic events (React.MouseEvent, etc.).
Removed the touch event handling, for now at least, because touch events do not have a clientX property so you need an alternative way to handle these. (This is the sort of issue that TypeScript is great at finding!).
Moved all top-level function calls into a useEffect hook, with a cleanup function (this is a best practice but is not strictly needed if this component is your entire app).
Add this point the code works! Woo hoo! Here is where we are at with those changes:
import React, { useEffect, useRef } from "react";
const styles = {
canvas: {
width: "100vw",
height: "100vh",
cursor: `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='40' height='48' viewport='0 0 100 100' style='fill:black;font-size:24px;'><text y='50%'>✍️</text></svg>") 5 25,auto`
}
};
function App() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const canvas = canvasRef.current;
const context = canvas?.getContext("2d");
let coord = { x: 0, y: 0 };
const resize = () => {
if (canvas) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
};
const reposition = (event: MouseEvent) => {
if (canvas) {
coord.x = event.clientX - canvas.offsetLeft;
coord.y = event.clientY - canvas.offsetTop;
}
};
const draw = (event: MouseEvent) => {
if (context) {
context.beginPath();
context.lineWidth = 5;
context.lineCap = "round";
context.strokeStyle = "#ACD3ED";
context.moveTo(coord.x, coord.y);
reposition(event);
context.lineTo(coord.x, coord.y);
context.stroke();
}
};
const start = (event: MouseEvent) => {
document.addEventListener("mousemove", draw);
reposition(event);
};
const stop = () => {
document.removeEventListener("mousemove", draw);
};
useEffect(
() => {
// call resize() once.
resize();
// attach event listeners.
window.addEventListener("resize", resize);
document.addEventListener("mousedown", start);
document.addEventListener("mouseup", stop);
// remove listeners on unmount.
return () => {
window.removeEventListener("resize", resize);
document.removeEventListener("mousedown", start);
document.removeEventListener("mouseup", stop);
};
},
[] // no dependencies means that it will be called once on mount.
);
return <canvas style={styles.canvas} ref={canvasRef} />;
}
export default App;
But we are still violating some principles of React by mutating this coord variable. So I thought about it a bit more and wondered if we could just return the x and y from a utility function rather than dealing with another state or ref. This draw function would not play nicely with React state because it uses the value before and after updating the coord variable and React setState calls are asynchronous.
I realized that it's really not necessary to beginPath() on every mouse movement. That is something which can be handled in start. Moving it there makes it so that draw only needs to know the current position and not the previous position.
Here's how that looks:
import React, { useEffect, useRef } from "react";
const styles = {
canvas: {
width: "100vw",
height: "100vh",
cursor: `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='40' height='48' viewport='0 0 100 100' style='fill:black;font-size:24px;'><text y='50%'>✍️</text></svg>") 5 25,auto`
}
};
function App() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const canvas = canvasRef.current;
const context = canvas?.getContext("2d");
const resize = () => {
if (canvas) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
};
const getCoord = (event: MouseEvent) => {
if (canvas) {
return {
x: event.clientX - canvas.offsetLeft,
y: event.clientY - canvas.offsetTop
};
} else {
return { x: 0, y: 0 };
}
};
const draw = (event: MouseEvent) => {
// extend the stroke to the current position.
if (context) {
const coord = getCoord(event);
context.lineTo(coord.x, coord.y);
context.stroke();
}
};
const start = (event: MouseEvent) => {
// begin path.
if (context) {
const coord = getCoord(event);
context.beginPath();
context.lineWidth = 5;
context.lineCap = "round";
context.strokeStyle = "#ACD3ED";
context.moveTo(coord.x, coord.y);
}
// listen for movements.
document.addEventListener("mousemove", draw);
};
const stop = () => {
// stop listening for movements.
document.removeEventListener("mousemove", draw);
};
useEffect(
() => {
// call resize() once.
resize();
// attach event listeners.
window.addEventListener("resize", resize);
document.addEventListener("mousedown", start);
document.addEventListener("mouseup", stop);
// remove listeners on unmount.
return () => {
window.removeEventListener("resize", resize);
document.removeEventListener("mousedown", start);
document.removeEventListener("mouseup", stop);
};
},
[] // no dependencies means that it will be called once on mount.
);
return <canvas style={styles.canvas} ref={canvasRef} />;
}
export default App;
Also is the document.removeEventListener and document.addEventListener with touchmove/mousemove a good solution or is there a better alternative in React? I use both so its possible to draw on touchscreens too.
There are some advantages to the document event approach, especially when it comes to touches that start off of the canvas and end on the canvas, or vice-versa. But your intention is for the canvas to be the entire screen so I'm not sure how relevant that is.
An alternative approach would be to use the onMouseDown, onMouseMove etc. properties of the React <canvas> element. This will attach the listeners directly to the canvas rather than to the document. The event variable in your handlers will be a React synthetic event. The event target will be the canvas which makes it easier to get the relative position of the event.
I'm defining the handlers inline so that the e variable automatically gets the correct type, which is React.MouseEvent<HTMLCanvasElement, MouseEvent>.
import React, { useEffect, useRef, useState } from "react";
const styles = {
canvas: {
cursor: `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='40' height='48' viewport='0 0 100 100' style='fill:black;font-size:24px;'><text y='50%'>✍️</text></svg>") 5 25,auto`
}
};
function App() {
const [isDrawing, setIsDrawing] = useState(false);
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(
() => {
// define the resize function, which uses the re
const resize = () => {
const canvas = canvasRef.current;
if (canvas) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
};
// call resize() once.
resize();
// attach event listeners.
window.addEventListener("resize", resize);
// remove listeners on unmount.
return () => {
window.removeEventListener("resize", resize);
};
},
[] // no dependencies means that it will be called once on mount.
);
return (
<canvas
style={styles.canvas}
ref={canvasRef}
onMouseDown={(e) => {
// know that we are drawing, for future mouse movements.
setIsDrawing(true);
const context = e.currentTarget.getContext("2d");
// begin path.
if (context) {
context.beginPath();
context.lineWidth = 5;
context.lineCap = "round";
context.strokeStyle = "#ACD3ED";
context.moveTo(e.nativeEvent.offsetX, e.nativeEvent.offsetY);
}
}}
onMouseMove={(e) => {
// only handle mouse moves when the mouse is already down.
if (isDrawing) {
const context = e.currentTarget.getContext("2d");
if (context) {
context.lineTo(e.nativeEvent.offsetX, e.nativeEvent.offsetY);
context.stroke();
}
}
}}
onMouseUp={() => {
// end drawing.
setIsDrawing(false);
}}
/>
);
}
export default App;
CodeSandbox Link
I want to change the bg color of my navbar div once I scroll down. And if it's already at the top, I want to revert back to the original. I tried doing this following some code I got from a website.
I am using tailwind for CSS and React.
Is there a better way to do this? And is there some kind of fault with this code?
export default function Home(props) {
const [scrollPosition, setScrollPosition] = useState(0);
const [top, setTop] = useState(true)
const handleScroll = () => {
const position = window.scrollY;
setScrollPosition(position);
if (scrollPosition == 0) {
setTop(true)
} else if (scrollPosition > 10) {
setTop(false)
} else if (scrollPosition < 10) {
setTop(true)
}
}
useEffect(() => {
window.addEventListener('scroll', handleScroll)
return () => {
window.removeEventListener('scroll', handleScroll)
}
})
return (
<div className={`${top ? 'bg-blue-500' : "bg-red-500"}`}>
</div>
);
}
Goal: When there is a price change,i want the price number to higlight for a few seconds with a color. I do that with the toogleClassName.
Problem: When the iteration is UP UP,or DOWN DOWN,the element already has that class and the CSS animation already ran.
JSX Code:
import React, { useEffect, useState, useRef } from "react";
import styles from "./CoinContainer.module.css";
function usePrevious(data) {
const ref = useRef();
useEffect(() => {
ref.current = data;
}, [data]);
return ref.current;
}
export default function CoinContainer({ coin, price }) {
const [priceUpdated, setPrice] = useState("");
const prevPrice = usePrevious(priceUpdated);
// Update price
useEffect(() => {
setInterval(priceUpdate, 20000);
}, []);
// Apply different flash-color style according to up$ or down$ from prev
const toggleClassName = () => {
return prevPrice > priceUpdated ? styles.redPrice : styles.greenPrice;
};
function priceUpdate() {
return fetch(
`https://api.coingecko.com/api/v3/simple/price?ids=${coin}&vs_currencies=usd`
)
.then((data) => data.json())
.then((result) => {
let key = Object.keys(result);
setPrice(result[key].usd);
});
}
return (
<div className={styles.padding}>
<h2>{coin}</h2>
{/* Here is the problem,i would like to remove the class after a few seconds,or edit the CSS code to retrigger the animation */}
<h3 className={toggleClassName()}>
{priceUpdated ? priceUpdated : price}$
</h3>
</div>
);
}
CSS Code
#keyframes upFadeBetween {
from {
color: green;
}
to {
color: white;
}
}
#keyframes downFadeBetween {
from {
color: red;
}
to {
color: white;
}
}
.redPrice {
animation: downFadeBetween 5s;
color: white;
}
.greenPrice {
animation: upFadeBetween 5s;
color: white;
}
Thanks so much for any feedback/help!
You could use another variable called e.g. reset
const [reset, setReset] = useState(false);
And then set this value at your methods using setTimeout
const toggleClassName = () => {
setTimeout(() => setReset(true), 6000);
return prevPrice > priceUpdated ? styles.redPrice : styles.greenPrice;
};
function priceUpdate() {
return fetch(
`https://api.coingecko.com/api/v3/simple/price?ids=${coin}&vs_currencies=usd`
)
.then((data) => data.json())
.then((result) => {
let key = Object.keys(result);
setReset(false);
setPrice(result[key].usd);
});
}
and finally
<h3 className={reset ? "" : toggleClassName()}>
you can do this
handleClick = event => event.target.classList.add('click-state');
or as react is js library so definitly it will support js DOM elements, well there is no doubt that it uses DOM also.
so you can do this also,
for adding class
document.getElementById("id here").classList.add("classname here");
for removing class
document.getElementById("id here").classList.remove("classname here");
OR
you can also use react States
You can use a variable that will be updated every time you trigger the fetch.
In this case, we will call it animationStyle
function CoinContainer({ coin, price }) {
const [priceUpdated, setPrice] = useState("")
const [animationStyle, setAnimationStyle] = useState("")
useEffect(() => {
setInterval(priceUpdate, 20000)
}, [])
function updateAnimationStyle(){
if (prevPrice > priceUpdated) {
setAnimationStyle("redPrice")
} else {
setAnimationStyle("greenPrice")
}
setTimeout(() => {
setAnimation("")
}, 5000)
}
function priceUpdate() {
return fetch(
`https://api.coingecko.com/api/v3/simple/price?ids=${coin}&vs_currencies=usd`
)
.then((data) => data.json())
.then((result) => {
let key = Object.keys(result)
setPrice(result[key].usd)
updateAnimationStyle()
})
}
return (
<div className={styles.padding}>
<h2>{coin}</h2>
<h3 className={animationStyle}>//you avoid doing the conditional rendering that might be a bit confusing
{priceUpdated ? priceUpdated : price}$
</h3>
</div>
)
}
Okay, so basically, the approach is to avoid too large conditional rendering inside the jsx.
the function updateAnimationStyle gets called every time the fetch is triggered.
depending on the condition, updateAnimationStyle will set redPrice or greenPrice animations to animationStyle.
the animationStyle will clean after 5 seconds through setTimeout function. "it can not be less than 5s because you have set the animation duration to 5s".
const [priceUpdated, setPrice] = useState("") it is better to avoid this type of naming for variables.
Instead, if you use const [priceUpdated, setPriceUpdated] = useState(""), it will be more readable.
Hope it works for you!
Regards.
I have a React Carousel that shows 3 elements at once. I would like to adjust this number of elements according to the size available. So for example
const RCarousel = ({items}) => {
const numItems = 3;
return (
<Carousel
numItemsPerView={numItems}
>
{
items.map(
(item) => <Item item={item} />
)
}
</Carousel>
)
}
I would like to change numItems to 2 if the RCarousel size is tablet size and 1 if is mobile size.
RCarousel may have a different width of the window width. Any suggestions how to make this? :)
You can use window.innerWidth and window.innerHight to get the size of the window.
Once you have the sizes, you can conditionally change it. I would stick the numItems in the useState and use useEffect to change it. Something along those lines
const [numItems, setNumItems] = useState(3);
const width = window.width;
useEffect(() => {
if (width > 800) {
setNumItems(3);
} else {
setNumItems(1);
}
}, [width])
Improving #szczocik's answer I was able to solve it using the following:
created a hook to get window size
useWindow.size.js
import {useState, useEffect} from 'react'
const useWindowSize = () => {
const [windowSize, setWindowSize] = useState({
width: undefined,
height: undefined,
});
useEffect(() => {
if (typeof window === 'undefined') return; //specific for gatsby or applications using webpack
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener("resize", handleResize);
handleResize();
return () => window.removeEventListener("resize", handleResize);
}, []);
return windowSize;
}
export default useWindowSize;
mycomponent.js
const windowSize = useWindowSize();
useEffect(() => {
const width = windowSize.width;
if (width >= 1200) {
if (numItems !== 3) {
setNumItems(3);
}
} else if (width > 900) {
if (numItems !== 2) {
setNumItems(2);
}
} else {
if (numItems !== 1) {
setNumItems(1);
}
}
}, windowSize)