How to change icons with React Spring? - javascript

I want to change the icon when I click on it with react spring. For example, when I click on "🤪", it will change into "😄". In the documentation of react spring, it's possible to make it with the transition props, but how do I toggle it with onClick?
https://www.react-spring.io/docs/props/transition
the following codes are provided by react spring
<Transition
items={toggle}
from={{ position: 'absolute', opacity: 0 }}
enter={{ opacity: 1 }}
leave={{ opacity: 0 }}>
{toggle =>
toggle
? props => <div style={props}>😄</div>
: props => <div style={props}>🤪</div>
}
</Transition>

create a button and change toggle value on click:
function App() {
const [toggle, setToggle] = React.useState(false);
return (
<>
<button onClick={() => setToggle(!toggle)}>toggle</button>
<Transition
items={toggle}
from={{ position: "absolute", opacity: 0 }}
enter={{ opacity: 1 }}
leave={{ opacity: 0 }}
>
{toggle =>
toggle
? props => <div style={props}>😄</div>
: props => <div style={props}>🤪</div>
}
</Transition>
</>
);
}

Related

How can I add animation to react state

I just want to add a fade in animation to next index. i found a package called "react transition group" but all tutorials were based on class components or redux I didn't understand anything.
const AboutTestimonials = () => {
const [index, setIndex] = useState<any>(0);
const [data] = useState(AddTestimonial);
const current = data[index];
return (
<div className="testimonials__container">
<div className="testimonials__description">
<h3>TESTIMONIALS</h3>
<p>{current.quote}</p>
<h5 className="author__testimonials">{current.postedby}</h5>
<h6 className="job__testimonials">{current.profession}</h6>
</div>
<div className="testimonials__pagination">
<Image
src={leftarrow}
alt="arrow"
onClick={() => setIndex(index > 0 ? index - 1 : index)}
className="pagination__arrows"
/>
<p>{index + 1} / 5</p>
<Image
src={rightarrow}
alt="arrow"
onClick={() => setIndex(index < 4 ? index + 1 : index)}
className="pagination__arrows"
/>
</div>
SwitchTransition waits for the old child to exit then render the new child as mentioned in the react transition group website.
there are two modes:
in-out
out-in
the important factor is changing the key prop of the child component.child component could be CSSTransition or Transition.if you want the transition changes simultaneously you can use the TransitionGroup.
side note: if you want to use the AddTestimonial in your component and you don't want to change the state (I noticed there is no second argument for setting the data), there is no need to declare a useState.it is much better to set AddTestimonial as a prop on AboutTestimonials component
import { CSSTransition, SwitchTransition } from 'react-transition-group';
const AboutTestimonials = () => {
const [index, setIndex] = useState<any>(0);
const [data] = useState(AddTestimonial);
const current = data[index];
return (
<div className="testimonials__container">
<div className="testimonials__description">
<h3>TESTIMONIALS</h3>
<SwitchTransition mode={'out-in'} >
<CSSTransition
key={index}
timeout={300}
classNames="fade"
>
<>
<p>{current.quote}</p>
<h5 className="author__testimonials">{current.postedby}</h5>
<h6 className="job__testimonials">{current.profession}</h6>
</>
</CSSTransition>
</SwitchTransition>
</div>
<div className="testimonials__pagination">
<Image
src={leftarrow}
alt="arrow"
onClick={() => setIndex(index > 0 ? index - 1 : index)}
className="pagination__arrows"
/>
<p>{index + 1} / 5</p>
<Image
src={rightarrow}
alt="arrow"
onClick={() => setIndex(index < 4 ? index + 1 : index)}
className="pagination__arrows"
/>
</div>
)}
css:
.fade-enter {
opacity: 0;
}
.fade-enter-active {
opacity: 1;
transition: opacity 300ms;
}
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: opacity 300ms;
}

How do I use AnimatePresence with AnimatedSharedLayout in framer motion?

I have a container which can possible display several child containers based on state. When the state changes (based on a button click), I would like:
The current container to fade out
The height of the main container to animate to the be the height of its new child container
The new child container to fade in
I feel like I'm on the right track with my demo, but the AnimatePresence seems to be ignored as none of the opacity animations are working. I'm hoping someone can please take a look and let me know what I need to adjust?
https://codesandbox.io/s/animating-shared-layouts-with-content-changing-h8hvq?file=/src/App.js
function Home() {
return (
<div>
<div className="row" />
<div className="row" />
</div>
);
}
function Contact() {
return (
<div>
<div className="row" />
<div className="row" />
<div className="row" />
<div className="row" />
<div className="row" />
</div>
);
}
const sections = {
home: "Home",
contact: "Contact"
};
export default function App() {
const [activeSection, setActiveSection] = useState(sections.home);
return (
<div className="outer-container">
<AnimateSharedLayout>
<motion.div layout initial={{ borderRadius: 25 }}>
<h3>Text on top</h3>
<AnimatePresence key={activeSection} exitBeforeEnter initial={false}>
<motion.div
initial={{ opacity: 0 }}
animate={{
opacity: 1,
transition: { delay: 1.2, duration: 1 }
}}
exit={{
opacity: 0,
transition: { delay: 0, duration: 1 }
}}
>
<div className="inner-container">
<div className="content">
<div className="avatar" />
{activeSection === sections.home && <Home />}
{activeSection === sections.contact && <Contact />}
</div>
</div>
</motion.div>
</AnimatePresence>
</motion.div>
</AnimateSharedLayout>
<button
type="button"
onClick={() =>
setActiveSection(
activeSection === sections.home ? sections.contact : sections.home
)
}
>
Go to {activeSection === sections.home ? "Contact" : "Home"} section
</button>
</div>
);
}

Why is useState returning undefined?

I'm attempting to use useState to alter the display type in my styled components. When attempting to use my code the display type is not altered and my variable "displayType" is undefined.
I've attempted altering what my setStyle() function returns, but I am starting to see this is a larger problem that I'd like to understand better.
When I print the value to the console in index.js everything works fine. However I just get undefined when I try to use displayType in StoreElements.js
src/pages/store.js
const [displayType, setDisplayType] = useState("none");
const setStyle = (displayType) => {
setDisplayType(displayType);
console.log(displayType)
};
const [isOpen, setIsOpen] = useState(false)
const toggle = () => {
setIsOpen(!isOpen)
}
return (
<div>
<Sidebar isOpen={isOpen} toggle={toggle} />
<Navbar toggle={toggle} />
<Store setStyle={setStyle} displayType={displayType}></Store>
<Footer />
</div>
)
}
export default StorePage
src/store/index.js
const Store = ({displayType, setStyle}) => {
return (
<>
<AboutBg style={{ backgroundImage: `url(${BgPic})` }}></AboutBg>
<StoreContainer>
<StoreWrapper>
<Title>Store</Title>
<ItemsContainer>
<ItemWrapper
onMouseEnter={() => setStyle("hoodie")}
onMouseLeave={() => setStyle("none")}
>
<ImgWrapper>
<ImgLink to="/about">
<MerchImg src={frontHoodie}></MerchImg>
</ImgLink>
</ImgWrapper>
<TextWrapper>
<MerchText>Hoodie text</MerchText>
<HoodiePriceText>price</HoodiePriceText>
</TextWrapper>
</ItemWrapper>
<ItemWrapper
onMouseEnter={() => setStyle("sweats")}
onMouseLeave={() => setStyle("none")}
>
<ImgWrapper>
<ImgLink to="/tournaments">
<MerchImg src={frontSweats}></MerchImg>
</ImgLink>
</ImgWrapper>
<TextWrapper>
<MerchText>Sweats text</MerchText>
<SweatsPriceText displayType={displayType}>
price
</SweatsPriceText>
</TextWrapper>
</ItemWrapper>
<ItemWrapper
onMouseEnter={() => setStyle("shirt")}
onMouseLeave={() => setStyle("none")}
>
<ImgWrapper>
<ImgLink to="/">
<MerchImg src={frontShirt}></MerchImg>
</ImgLink>
</ImgWrapper>
<TextWrapper>
<MerchText>Shirt text</MerchText>
<ShirtPriceText>price</ShirtPriceText>
</TextWrapper>
</ItemWrapper>
<ItemWrapper
onMouseEnter={() => setStyle("mousepad")}
onMouseLeave={() => setStyle("none")}
>
<ImgWrapper>
<ImgLink to="/">
<MerchImg src={mousepadFront}></MerchImg>
</ImgLink>
</ImgWrapper>
<TextWrapper>
<MerchText>mouspad text</MerchText>
<MousepadPriceText>price</MousepadPriceText>
</TextWrapper>
</ItemWrapper>
</ItemsContainer>
</StoreWrapper>
</StoreContainer>
<div>
{listItems}
{cartItems}
Total: ${cartTotal}
{cartItems.length}
</div>
</>
);
};
export default Store;
src/store/StoreElements.js
export const HoodiePriceText = styled.h4`
color: red;
position: absolute;
top: 365px;
transition: 0.8s all ease;
display: ${({ displayType }) => {
if (displayType === "hoodie") {
console.log("working");
return "block";
} else {
console.log({displayType})
return "none";
}
}};
`;
export const ShirtPriceText = styled.h4`
color: red;
position: absolute;
top: 365px;
transition: 0.8s all ease;
`;
export const MousepadPriceText = styled.h4`
color: red;
position: absolute;
top: 365px;
transition: 0.8s all ease;
`;
export const SweatsPriceText = styled.h4`
color: red;
position: absolute;
top: 365px;
transition: 0.8s all ease;
`;
In your styled component usage, you should bind the property displayType:
<HoodiePriceText displayType={displayType}>price</HoodiePriceText>
Thus, you should able get displayType in styled component!
setDisplayType is triggering the state change and causes a re-render of the function. It does not modify the value of the variable displayType. The value displayType is still undefined directly after calling setDisplayType, because it only gets its value after the function re-runs the useState-line.
const [displayType, setDisplayType] = useState("none");
// displayType can only get a new value here

Animating Card components in my slider in react app not working

I have a slider in my react app that slides my card components when any of the icon buttons are clicked. When clicked I want each card to show the fade animation but it only works when the page reloads. I am using css animation. I am using the following code
<div className={classes.root}>
<div className={classes.slider}>
<Card image={carddata[slider].image} slider />
</div>
<ArrowBackIosIcon
fontSize="large"
style={{ left: 0 }}
className={classes.arrow}
onClick={prevSlide}
/>
<ArrowForwardIosIcon
fontSize="large"
style={{ right: 0 }}
className={classes.arrow}
onClick={nextSlide}
/>
</div>
The CSS class is following
slider: {
animation: 'fadeeffect 3000ms linear 250ms',
},
#keyframes fadeeffect: {
"0%": {
opacity: 0,
},
"25%": {
opacity: 0.25,
},
"50%": {
opacity: 0.5,
},
"100%": {
opacity: 1,
},
},
You must add slider class when you want to show the image, but you are adding it on the load e.g on click. I have provided a sandbox example to show you how to do it:
import { useRef } from "react";
const myRef = useRef(null);
const handleClick = ()=>{
myRef.current.classList.add('slider');
}
<div className={classes.root}>
<button onClick={handleClick}>Show The Element</button>
<div /*className={classes.slider}*/ ref={myRef} >
<Card image={carddata[slider].image} slider />
</div>
<ArrowBackIosIcon
fontSize="large"
style={{ left: 0 }}
className={classes.arrow}
onClick={prevSlide}
/>
<ArrowForwardIosIcon
fontSize="large"
style={{ right: 0 }}
className={classes.arrow}
onClick={nextSlide}
/>
</div>

Mouse pointer cannot detect element to trigger onClick()

From the snippet below, you can see that there are 2 Box components that are "underneath" the BigBox component, hence the mouse cannot detect them to trigger onClick(). Is there a way for the mouse to detect those 2 components.
I understand that you can just render the BigBox component first then Box like what I did with the <Box id="yes onClick"> component
However, for the project I'm working on, I specifically need to render a <Box>-like component first then a <BigBox>-like component.
const boxStyle = {
position: "absolute",
border: "1px #999 solid",
borderRadius: "10px",
textAlign: "center",
}
const Box = (props) => {
return (
<div style = {{ ...boxStyle, left: props.left, top: props.top, height: 50, width: 50 }}
onClick = {() => console.log("clicked")} > { props.id } </div>
);
};
const BigBox = (props) => {
return (
<div style = {{ ...boxStyle, left: props.left, top: props.top, height: 200, width: 200 }}> Big Box </div>
);
}
const App = () => {
return (
<div >
<Box id="no onClick 1" left={40} top={50}></Box>
<Box id="no onClick 2" left={80} top={100}></Box>
<BigBox left={10} top={10}></BigBox>
<Box id="yes onClick" left={150} top={150}></Box>
</div>
);
};
ReactDOM.render( <App/> ,
document.getElementById('root')
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Option 1 - pointer-events
You can set the CSS Property pointer-events of the BigBox style to none.
const boxStyle = {
position: "absolute",
border: "1px #999 solid",
borderRadius: "10px",
textAlign: "center",
}
const Box = (props) => {
return (
<div style = {{ ...boxStyle, left: props.left, top: props.top, height: 50, width: 50 }}
onClick = {() => console.log("clicked")} > { props.id } </div>
);
};
const BigBox = (props) => {
return (
<div style = {{ ...boxStyle, pointerEvents: 'none', left: props.left, top: props.top, height: 200, width: 200 }}> Big Box </div>
);
}
const App = () => {
return (
<div >
<Box id="no onClick 1" left={40} top={50}></Box>
<Box id="no onClick 2" left={80} top={100}></Box>
<BigBox left={10} top={10}></BigBox>
<Box id="yes onClick" left={150} top={150}></Box>
</div>
);
};
ReactDOM.render( <App/> ,
document.getElementById('root')
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Option 2 - z-index
Another solution would be to set the z-index of you Boxes to something bigger than the z-index of the BigBox component. That way the BigBox can still receive pointer events.
Option 3 - Use the layout hierarchy
Probably the most idiomatic way would be, to put your boxes inside BigBox.

Categories

Resources