Change background-color once scrolled with react? - css

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>
);
}

Related

How can I change state in client-side in NEXT.js?

I have a simple tab component in NEXT. Here's my component code:
const Tabs = ({ tabItems }) => {
const setActiveTab = (tabItem) => {
for (var i = 0; i < tabItems.length; i++) {
tabItems[i].active = false;
}
tabItem.active = true;
}
return <div>
{
tabItems.map(tabItem => <div
className={tabItem.active ? "active-tab" : ""}
onClick={() => setActiveTab(tabItem)}
>{tabItem.title}</div>)
}
</div>
}
However, when I click on tab items, the state does not change.
How can I change state in client-side in NEXT?
You can't change state from a parent like this. The way that you're doing, you're only changing the value of tabItem inside the children component.
You could use Context API to achieve the desired result, or just pass down a function from the parent, which would be the best if you're not passing props down too many components.
Code example:
parent.tsx:
export const Parent = () => {
const [tabItems, setTabItems] = useState()
function changeTabItems(newValue) {
setTabItems(newValue)
}
return <Child changeTabItems={changeTabItems} tabItems={tabItems}/>
}
child.tsx:
export const Child = ({changeTabItems, tabItems}) => {
function setActiveTab(tabItem) {
let aux = tabItems
for (var i = 0; i < tabItems.length; i++) {
aux[i].active = false;
}
aux.active = true;
changeTabItems({...aux})
}
}

React Wait until transition end for vertical scroll

I'm trying to make vertical scroll page looks like fullpage.js with react
https://alvarotrigo.com/fullPage/
Page scroll works well but having hard time to prevent double scroll..
I want to make scroll doesn't work until finish move to next page.
Using setTimeout and clearTimeout works well but it has pre delay before moving pages.
I don't like that so tried to use onTransitionEnd to detect change but doesn't work well.
Currently my code is like this
const Container = () => {
const [index, setIndex] = useState(0);
const [isAnimating, setAnimating] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const animatingRef = useRef(false);
const indexRef = useRef(0);
indexRef.current = index;
useEffect(() => {
if (!isAnimating) {
const updater = (e: WheelEvent) => {
setAnimating(true);
let dir = e.deltaY;
if (dir < 0) {
indexRef.current > 0 && setIndex(indexRef.current - 1);
} else {
indexRef.current < 2 && setIndex(indexRef.current + 1);
}
};
window.addEventListener('wheel', e => updater(e));
return () => {
window.removeEventListener('wheel', e => updater(e));
};
}
}, [isAnimating]);
useEffect(() => {
if (ref && ref.current) {
ref.current.style.transition = 'all 0.5s ease-in-out';
ref.current.style.transform = `translate3d(0,-${index * 100}%,0)`;
}
}, [index]);
return (
<Wrapper>
<ViewContainer ref={ref} onTransitionEnd={() => setAnimating(false)}>
<View color="orange" />
<View color="red" />
<View color="yellow" />
</ViewContainer>
</Wrapper>
);
};

window width in react

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>

I want to remove a className after a few seconds, or re trigger the class animation when condition is met

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.

How to make a responsive number os slides in a react carousel

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)

Resources