Styled components onClick rotate element - javascript

I'm trying to add a rotation to an element when clicked. Each time I want to click on the element the element should rotate. I'm rotating the element using CSS transform property. Bellow is my styled component:
const IconButtonWrapper = styled.div`
transform: rotate(0deg);
overflow: hidden;
transition: all 0.3s ease-out;
${({ rotate }) => rotate && `transform: rotate(360deg)`};
`;
And I have the React component that renders the element and has the onClick event:
const IconButtonContainer = () => {
const [rotate, setRotate] = useState(false);
const handleClick = () =>
setRotate(
(prevState) => ({ rotate: !prevState.rotate }),
() => console.log(this.state.rotate)
);
return (
<IconButtonWrapper rotate={rotate} onClick={handleClick}>
<IconButton />
</IconButtonWrapper>
);
};
When I click the first time everything works as expected, but when I click the second time, doesn't work anymore. I feel I should return to default state rotate: false after the transition, but how do I do that in styled components?
Here is a link to a working Sandbox

Technically rotate is already a boolean so prevState should be a boolean as well. Also you were trying to set an object to the rotate state value instead of a simple boolean.
Based on that you only need to negate prevState and not prevState.rotate as:
setRotate(prevState => !prevState)
See the code modification from !prevState.rotate to !prevState.
Also suggested read is Using the State Hook. It's a bit different than class components.

Try this out.
const handleClick = () => setRotate(!rotate);

The problem is solving by changing the code of handleClick to:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
import "./styles.css";
const IconButton = () => <button>Icon</button>;
const IconButtonWrapper = styled.div`
transform: rotate(0deg);
overflow: hidden;
transition: all 0.3s ease-out;
${({ rotate }) => rotate && `transform: rotate(360deg)`};
`;
const IconButtonContainer = () => {
const [rotate, setRotate] = useState(false);
const handleClick = () => setRotate((prevState) => (!prevState ));
return (
<IconButtonWrapper rotate={rotate} onClick={handleClick}>
<IconButton />
</IconButtonWrapper>
);
};
function App() {
return (
<div className="App">
<IconButtonContainer />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
prevState already has the latest value of rotate but you were converting it to an object. setRotate((prevState) => (!prevState )); will do.

Related

How do I get the state of the parent component in a child styled component using tagged template literals?

I have a react component App as following, it has a state WHRatio, and my div component will use the WHRatio value to calculate the height value. In my following way, it can work, it can get the parent component state WHRatio successfully.
import React, { useState } from "react";
export default function App() {
const sizeWidth = 100;
const [WHRatio, setWHRatio] = useState(1);
const getContainerHeight = () => Math.floor(sizeWidth / WHRatio);
const incWHRatio = () => setWHRatio(WHRatio + 1);
return (
<>
<div
style={{
width: sizeWidth,
height: getContainerHeight(),
backgroundColor: "orange"
}}
>
</div>
<button onClick={incWHRatio}>Inc WHRatio</button>
</>
);
}
As we known, styled-components utilises tagged template literals to style the components.
// something like this
const Container = styled.div`
background-color: orange;
width: 100px;
height: 200px;
`
I want to use the tagged template literals to style my component rather than the inline style. So how do I get the state of the parent component in a child styled component using tagged template literals?
Online Demo
You do that by passing props value to custom style, for example:
const Container = styled.div`
background-color: orange;
width: 100px;
height: ${props => props.customHeight + "px"}
`
Full Example:
import React, { useState } from "react";
import styled from "styled-components";
const Container = styled.div`
background-color: orange;
width: 100px;
height: ${props => props.customHeight + "px"}
`
export default function App() {
const sizeWidth = 100;
const [WHRatio, setWHRatio] = useState(1);
const getContainerHeight = () => Math.floor(sizeWidth / WHRatio);
const incWHRatio = () => setWHRatio(WHRatio + 1);
return (
<>
<Container
customHeight={getContainerHeight()}
></Container>
<button onClick={incWHRatio}>Inc WHRatio</button>
</>
);
}
Also, you can use css feature and pass it to styled component, for more details and examples you can check this url.
Example 2:
import styled, { css, keyframes } from 'styled-components'
const animation = keyframes`
0% {
opacity: 0;
}
100 {
opacity: 1;
}
`
const animationRule = css`
${animation} 1s infinite alternate;
`
const Component = styled.div`
animation: ${animationRule};
`
DEMO URL
did you try some like this :
you pass your sizeWidth like
<Container sizeWidth={sizeWidth}/>
then in your styled component will be like:
const Container = styled.div`
width: ${(props) => props.sizeWidth + "px"};
`

Creating an Observer component in React - Passing props to the children

I'm looking to build an intersection observer component in a personal Gatsby project. The reason for it is the animation and trigger is the same in different areas of the site. What I have so far:
// Observer.js
import React, { useEffect, useRef } from "react";
import { useAnimation } from "framer-motion";
const Observer = ({ children }) => {
const controls = useAnimation();
const ref = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
controls.start({
opacity: 1,
transition: { duration: 0.75, delay: 0.75 },
});
}
},
{
root: null,
rootMargin: "0px",
threshold: 0.1,
}
);
if (ref.current) {
observer.observe(ref.current);
}
}, [ref]);
return <div ref={ref}>{children}</div>;
};
export default Observer;
What I am looking to do is access controls in the children of the Observer.js component.
// ChildComponent.js
const ChildComponent= (props) => {
return (
<Observer>
<div>
<motion.div
initial={{ opacity: 0 }}
animate={props.controls}
>
<h2>This will animate on scroll</h2>
</motion.div>
</div>
</Observer>
);
};
export default ChildComponent;
I have seen answers such as - Pass props from layout to children in Gatsby - but I get the error props.children is not a function when attempting this.
Is what I am looking to do possible, or is there a better way of achieving this?

Property in Component State not updating quick enough

I'm trying to display text when I hover over an icon, but if you hover the icons quickly it'll get stuck on displaying the text instead of displaying the icon (default state)
ex: https://giphy.com/gifs/UsS4JcRJGV5qfCI5VI
Skills Component:
import React, { useState } from 'react';
import { UserIcon } from './AboutBtnStyling';
import IconText from '../../../IconText';
const AboutBtn = () => {
const [hover, setHover] = useState(false);
const onHover = () => {
setHover(true)
}
const onLeave = () => {
setHover(false)
}
return (
<div onMouseEnter={onHover} onMouseLeave={onLeave} role="button">
{hover ? <IconText text="ABOUT" /> : <UserIcon /> }
</div>
)
}
export default AboutBtn;
Then I hoped converting it to a class component would help, bc of stale closure problem associate with useState hook
import React, { Component } from 'react';
import { SkillIcon } from './SkillsBtnStyling';
import IconText from '../../../IconText';
class SkillsBtn extends Component {
constructor(props) {
super(props);
this. state = { hover: false }
}
onHover = () => {
this.setState({ hover: true })
}
onLeave = () => {
this.setState({ hover: false })
}
render() {
return (
<div onMouseEnter={this.onHover} onMouseLeave={this.onLeave} role="button">
{this.state.hover ? <IconText text="SKILLS" /> : <SkillIcon /> }
</div>
)
}
}
export default SkillsBtn;
Would greatly appreciate any insight! I really want to solve this problem, instead of resorting to achieving this effect using CSS
An important aspect of useState is that it is asynchronous. I believe this is causing your code to act a bit buggy. I would add more decisiveness to your setState calls and set it (true/false) based on mouse position rather than toggle.
import React, { useState } from 'react';
import { SkillsButton } from './SkillsBtnElements'
const SkillsBtn = () => {
const [hover, setHover] = useState(false);
const onHover = () => {
setHover(!hover)
}
return (
<div onMouseEnter={() => setHover(true)} onMouseLeave={() =>
setHover(false)} role="button" tabIndex='-3' >
{ hover ? "SKILLS" : <SkillsButton /> }
</div>
)
}
export default SkillsBtn;

Working with react-spring and styled-components

I can't get react-spring to work. I'm fairly new to this so I have no idea what is going wrong. I'm trying to make navbar appear from top to bottom to 40vh, but it doesn't appear to be recognizing the props passed. I used create-react-app and react-spring 8.0.27
App.js:
const App = () => {
const [open, setOpen] = useState(false);
const navprops = useSpring({
from: {height: "0"},
to: {height: "40vh"}
})
return (
<Fragment>
{open ? <Navbar style={navprops}/> : null}
</Fragment>
Navbar.js:
const NavBar = styled(animated.nav)`
width: 100%;
`;
const Navbar = (props) => {
return (
<NavBar style={props.style}>
</NavBar>
);
};
This is basically the code. There are more style props but I guess it's irrelevant to functionality.
animated and useSpring are imported in both files for testing. Thank you for your help.
Here is my solution,
Demo Link
Navbar.js
import React from "react";
import styled from "styled-components";
import { animated } from "react-spring";
const NavBar = styled(animated.nav)`
width: 100%;
background: red;
`;
export default (props) => {
return <NavBar style={props.style}></NavBar>;
};
App.js
import React, { useState } from "react";
import { useTransition, config } from "react-spring";
import Navbar from "./Navbar";
export default function App() {
const [open, setOpen] = useState(false);
// const navprops = useSpring({
// from: { height: "0" },
// to: { height: "40vh" },
// config: config.wobbly
// });
const transitions = useTransition(open, null, {
initial: { height: "0px" }, //Not required
from: { height: "0px" },
enter: { height: "40vh" },
leave: { height: "0px" },
config: config.wobbly //More configs here https://www.react-spring.io/docs/hooks/api
});
return (
<div className="App">
{transitions.map(
({ item, key, props }) => item && <Navbar key={key} style={props} />
)}
<br />
<br />
<button onClick={() => setOpen(!open)}>Toggle Navbar</button>
</div>
);
}
I do not think useSpring will work on unmounted component. You were trying to animate an unmounted component.
According to documentation, useTransition can be used to animate mounting of unmounted components.
The syntax is little complicated, but they have made the syntax simpler in version 9(release candidate) of react-spring Link Here

Detect click outside component react hooks

I am trying to use react hooks to determine if a user has clicked outside an element. I am using useRef to get a reference to the element.
Can anyone see how to fix this. I am getting the following errors and following answers from here.
Property 'contains' does not exist on type 'RefObject'
This error above seems to be a typescript issue.
There is a code sandbox here with a different error.
In both cases it isn't working.
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const Menu = () => {
const wrapperRef = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState(true);
// below is the same as componentDidMount and componentDidUnmount
useEffect(() => {
document.addEventListener('click', handleClickOutside, true);
return () => {
document.removeEventListener('click', handleClickOutside, true);
};
}, []);
const handleClickOutside = event => {
const domNode = ReactDOM.findDOMNode(wrapperRef);
// error is coming from below
if (!domNode || !domNode.contains(event.target)) {
setIsVisible(false);
}
}
return(
<div ref={wrapperRef}>
<p>Menu</p>
</div>
)
}
the useRef API should be used like this:
import React, { useState, useRef, useEffect } from "react";
import ReactDOM from "react-dom";
function App() {
const wrapperRef = useRef(null);
const [isVisible, setIsVisible] = useState(true);
// below is the same as componentDidMount and componentDidUnmount
useEffect(() => {
document.addEventListener("click", handleClickOutside, false);
return () => {
document.removeEventListener("click", handleClickOutside, false);
};
}, []);
const handleClickOutside = event => {
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
setIsVisible(false);
}
};
return (
isVisible && (
<div className="menu" ref={wrapperRef}>
<p>Menu</p>
</div>
)
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I have created this common hook, which can be used for all divs which want this functionality.
import { useEffect } from 'react';
/**
*
* #param {*} ref - Ref of your parent div
* #param {*} callback - Callback which can be used to change your maintained state in your component
* #author Pranav Shinde 30-Nov-2021
*/
const useOutsideClick = (ref, callback) => {
useEffect(() => {
const handleClickOutside = (evt) => {
if (ref.current && !ref.current.contains(evt.target)) {
callback(); //Do what you want to handle in the callback
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
});
};
export default useOutsideClick;
Usage -
Import the hook in your component
Add a ref to your wrapper div and pass it to the hook
add a callback function to change your state(Hide the dropdown/modal)
import React, { useRef } from 'react';
import useOutsideClick from '../../../../hooks/useOutsideClick';
const ImpactDropDown = ({ setimpactDropDown }) => {
const impactRef = useRef();
useOutsideClick(impactRef, () => setimpactDropDown(false)); //Change my dropdown state to close when clicked outside
return (
<div ref={impactRef} className="wrapper">
{/* Your Dropdown or Modal */}
</div>
);
};
export default ImpactDropDown;
Check out this library from Andarist called use-onclickoutside.
import * as React from 'react'
import useOnClickOutside from 'use-onclickoutside'
export default function Modal({ close }) {
const ref = React.useRef(null)
useOnClickOutside(ref, close)
return <div ref={ref}>{'Modal content'}</div>
}
An alternative solution is to use a full-screen invisible box.
import React, { useState } from 'react';
const Menu = () => {
const [active, setActive] = useState(false);
return(
<div>
{/* The menu has z-index = 1, so it's always on top */}
<div className = 'Menu' onClick = {() => setActive(true)}
{active
? <p> Menu active </p>
: <p> Menu inactive </p>
}
</div>
{/* This is a full-screen box with z-index = 0 */}
{active
? <div className = 'Invisible' onClick = {() => setActive(false)}></div>
: null
}
</div>
);
}
And the CSS:
.Menu{
z-index: 1;
}
.Invisible{
height: 100vh;
left: 0;
position: fixed;
top: 0;
width: 100vw;
z-index: 0;
}

Categories