I'm having trouble creating a simple animation in React-Pose. The two problems are
1) I can't get the animation to revert to the initial condition. The hovering variable is changing to false when the mouse leaves, but it the animation doesn't change back.
2) I can't manipulate the animation, I wanted to have a longer duration and maybe an ease out or something, but its just an instant snap to the hovered status.
import React, { useState } from 'react';
import styled from 'styled-components';
import posed from 'react-pose';
import { render } from 'react-dom';
const UpFor = () => {
const [hovering, setHovering] = useState(false);
const HoverContainer = posed.div({
hoverable: true
})
const Container = styled(HoverContainer)`
font-family: 'Baumans';
font-size: 220px;
display: flex;
cursor: pointer;
`
const Up = styled.div`
color: #81D6E3;`
const Four = styled.div`
color: #FF101F
`
const Fours = styled.div`
display: flex;
`
const MirroredFour = posed.div({
unhovered: {transform: 'rotatey(0deg)'},
hovered: {transform: 'rotateY(180deg)',
transition: {
type: 'tween',
duration: '2s'
}}
})
const SecondFour = styled(MirroredFour)`
color: #FF101F
position: absolute;
transform-origin: 67%;
`
return (
<Container onMouseEnter={() => {setHovering({ hovering: true }), console.log(hovering)}}
onMouseLeave={() => {setHovering({ hovering: false }), console.log(hovering)}}>
<Up>Up</Up><Fours><Four>4</Four>
<SecondFour pose={hovering ? "hovered" : "unhovered"}
>4</SecondFour></Fours>
</Container>)
}
export default UpFor
There were two main issues with your code:
duration does not appear to support string values like '2s'. I changed this to 2000.
You were defining your components (e.g. using styled.div, posed.div) inside of your render function. This caused these components to be treated by React as unique component types with each re-render. This results in those components being unmounted and re-mounted each render which prevents transitions from working since the element isn't changing -- instead it is being replaced by a new component of a different type.
Below is a working version of your code which moves the component definitions outside of the render (UpFor) function. You can play around with it in the sandbox provided.
import React, { useState } from "react";
import styled from "styled-components";
import posed from "react-pose";
const Container = styled.div`
font-family: "Baumans";
font-size: 220px;
display: flex;
cursor: pointer;
`;
const Up = styled.div`
color: #81d6e3;
`;
const Four = styled.div`
color: #ff101f;
`;
const Fours = styled.div`
display: flex;
`;
const MirroredFour = posed.div({
unhovered: { transform: "rotateY(0deg)" },
hovered: {
transform: "rotateY(180deg)",
transition: {
type: "tween",
duration: 2000
}
}
});
const SecondFour = styled(MirroredFour)`
color: #FF101F
position: absolute;
transform-origin: 67%;
`;
const UpFor = () => {
const [hovering, setHovering] = useState(false);
console.log("hovering", hovering);
return (
<Container
onMouseEnter={() => {
setHovering(true);
}}
onMouseLeave={() => {
setHovering(false);
}}
>
<Up>Up</Up>
<Fours>
<Four>4</Four>
<SecondFour pose={hovering ? "hovered" : "unhovered"}>4</SecondFour>
</Fours>
</Container>
);
};
export default UpFor;
Related
strong textI'm trying to make a Pokedex App with an animation when we click on a card.
I have a Component PokemonList who render PokemonCard.
When i click on PokemonCard, I navigate to /{pokemonName} and an detail component shows up with an animation.
The Detail component is rendered with of React router
My PokemonCard and my PokemonView (Detail pannel) are in the same Framer Motion LayoutGroup. My cards have the layoutId card-${pokemonInfo.name} and my PokemonView have too. On the opening, i fetch current pokemon with the location.pathname and fill detail pannel with infos.
PokemonView.ts
import { LayoutGroup, motion } from "framer-motion"
import { useLocation, useNavigate } from "react-router-dom"
import styled from "styled-components"
import { colours } from "../../data/typeColors"
import { hexToRgb } from "../../helpers/hexToRgb"
import { useDisableScroll } from "../../hooks/useDisableScroll"
import {
useGetPokemonInfoQuery,
useGetPokemonSpeciesInfoQuery,
} from "../../redux/api/api"
import PokemonInfoPannel from "./PokemonInfoPannel"
import ViewTab from "./Tabs/ViewTab"
const PokemonViewContainer = styled.div`
height: 100%;
width: 100vw;
background-color: ${(props: { background: string }) =>
hexToRgb(props.background, 0.5)};
position: fixed;
top: 0;
display: flex;
justify-content: center;
align-items: center;
`
const PokemonViewStyle = styled(motion.div)`
background-color: ${(props: { background: string }) => props.background};
height: 100%;
width: 100%;
min-height: 680px;
max-width: 600px;
max-height: 800px;
overflow: hidden;
opacity: 1;
#media (min-width: 600px) {
border-radius: 2rem;
box-shadow: 0 0 10px 0 rgba(255, 255, 255, 0.5);
}
#media (max-width: 600px) {
max-height: 100vh;
}
`
const PokemonView = () => {
console.log("PokemonView")
const navigate = useNavigate()
useDisableScroll()
const location = useLocation()
const { data: pokemonInfo } = useGetPokemonInfoQuery(
location.pathname.split("/")[1]
)
const { data: pokemonSpeciesInfo } = useGetPokemonSpeciesInfoQuery(
location.pathname.split("/")[1]
)
return (
<>
<PokemonViewContainer
id="pokemon-view-container"
background={colours[pokemonInfo!.type[0]].background}
onClick={(e) => {
navigate("/")
}}
>
<LayoutGroup id="card-swap-animation">
<PokemonViewStyle
layoutId={`card-${pokemonInfo.name}`}
id="pokemon-view"
background={colours[pokemonInfo!.type[0]].background}
onClick={(e) => {
e.stopPropagation()
}}
>
{/* <PokemonInfoPannel
pokemonDetailed={{
info: pokemonInfo!,
speciesInfo: pokemonSpeciesInfo!,
}}
></PokemonInfoPannel>
<ViewTab
pokemonDetailed={{
info: pokemonInfo!,
speciesInfo: pokemonSpeciesInfo!,
}}
></ViewTab> */}
</PokemonViewStyle>
</LayoutGroup>
</PokemonViewContainer>
</>
)
}
export default PokemonView
With this I have the correct behavior. But, I have some typescript errors because my pokemonInfo and pokemonSpeciesInfo can be undefined (i fill them via RTK-Query).
So, I decided to add this before the return
if (!pokemonInfo || !pokemonSpeciesInfo) {
return null
}
But with this line, the first time i click on a card the animation doesn't work. I close detail pannel, open it again and the animation works.
Another think that I don't understand is that when I do that
const { data: pokemonInfo } = useGetPokemonInfoQuery(
location.pathname.split("/")[1]
)
console.log(pokemonInfo)
const { data: pokemonSpeciesInfo } = useGetPokemonSpeciesInfoQuery(
location.pathname.split("/")[1]
)
console.log(pokemonSpeciesInfo)
I see that the first request is never undefined but the second is always, then became fullfilled. I don't know if this is the problem.
Someone can help me ?
Thanks in advance
The source code is here : https://github.com/RomainHoffmann/Pokedex
UPDATE :
I understood why the first console was never null. It's because I do this request on my PokemonCard component, so the response is cached by RTK Query.
To fix my problem, I add the other query in my Card component, it works but I call it "in the void".
I have understand now thats returning <></> in the first render makes the problem but how could I do to avoid Typescript errors and render the component only when datas are fullfiled ?
I've been trying to figure out why does my text input loses focus after typing one character through questions here and I have found no luck.
this bug occured after i added some CSS code.
this is the code :
import React, { useState } from "react";
import { v4 as uuidv4 } from "uuid";
import styled from "styled-components";
const App = () => {
const [input, setInput] = useState("");
const [todos, setTodos] = useState([
{ name: "Feed the dog", id: uuidv4() },
{ name: "Help mother", id: uuidv4() },
{ name: "Study", id: uuidv4() },
]);
const StyledList = styled.div`
justify-content: center;
text-align: center;
align-items: center;
max-width: 40%;
margin: 8rem auto;
box-shadow: red;
min-height: 60vh;
border: 1px solid rgba(0, 0, 0, 0.3);
box-shadow: 30px 30px 20px rgba(0, 0, 0, 0.3);
`;
const StyledButtonList = styled.div`
display: flex;
justify-content: center;
`;
const StyledTodo = styled.div`
display: flex;
flex-direction: column;
width: 30%;
margin: auto;
`;
const StyledButton = styled.button`
margin-left: 2%;
`;
const StyledP = styled.p`
display: flex;
justify-content: space-between;
`;
const clearAll = () => {
if (window.confirm("Do you want to delete all?")) {
setTodos([]);
}
};
const deleteTask = (id) => {
setTodos(todos.filter((todo) => todo.id !== id));
console.log(id);
};
return (
<StyledList>
<h1>What are the plans for today?</h1>
<form>
<input
type="text"
placeholder="Add Todo"
value={input}
onChange={(e) => setInput(e.target.value)}
></input>
<button
onClick={(e) => {
e.preventDefault();
setTodos([
...todos,
{
name: input,
id: uuidv4(),
},
]);
}}
>
Add
</button>
<h1>Your todos:</h1>
</form>
<StyledTodo>
{todos.map((todo) => {
return (
<StyledP>
{todo.name}{" "}
<StyledButtonList>
<StyledButton onClick={() => deleteTask(todo.id)}>
x
</StyledButton>{" "}
</StyledButtonList>
</StyledP>
);
})}
</StyledTodo>
<button onClick={() => clearAll()}> Remove all </button>
</StyledList>
);
};
export default App;
I have tried setting a key inside the input as some answers suggested but I still could not find a fix for this.
You help is very much appreciated!
With styled-components library it might look like you've just added a bit of CSS, while in fact you declared several new React components. And you declared them inside the component that uses them. The implication of this, is that these styled components are re-declared on each render. Each time you type one character, state of App changes and React re-renders it, during the render it re-declares StyledList, when React compares component tree you had before with a new one and notices that it is different, because old StyledList is not the same as new StyledList, so React creates everything from scratch, new StyledList and new input inside it. And the new input is not focused, because why would it?
TLDR move all const Styled outside of the App
I would like to give Border-bottom as a function. What should I do?
This is the code that Border-bottom should appear.๐
import React from "react";
import { Header } from "../BaseLabel";
import { Link, withRouter } from "react-router-dom";
const Header = ({ location: { pathname } }) => {
const getStyle = (path) => {
return {
color: pathname === path ? "#191919" : "#B6B6B6",
borderBottom: pathname === path ? "#191919" : null,
};
};
return (
<>
<ShapMenu>
<ShapLinks to="/covid19" style={getStyle("/covid19")}> //Link
<Header title="์ฝ๋ก๋19" current={pathname === "/covid19"} />
</ShapLinks>
</ShapMenu>
</>
);
}
This is Header Styled-components๐
const ShapMenu = styled.div`
display: flex;
box-sizing: content-box;
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
scroll-behavior: smooth;
scrollbar-color: inherit;
cursor: pointer;
`;
const ShapLinks = styled(Link)``;
This is a reusable component code. This code is not only used on this screen because it is a reuse code.๐
import PropTypes from "prop-types";
import styled from "styled-components";
import React from "react";
export const Header = ({ title, children }) => {
return (
<>
<Title>{title}</Title>
<Items>{children}</Items>
</>
);
};
Header.propTypes = {
title: PropTypes.node,
children: PropTypes.object,
};
const Items = styled.div``;
const Title = styled.div`
margin-right: 14px;
font-size: 20px;
`;
This is the style property that I want to give to the title.๐
border-bottom: 2px solid
${(props) => (props.current ? "#191919" : "transparent")};
transition: border-bottom 0.5s ease-in-out;
The CSS styled rules appear to be correct. You should pass the current prop from Header to Title.
const Header = ({ current, title, children }) => { // <-- destructure current
return (
<>
<Title current={current}>{title}</Title> // <-- pass current prop
<Items>{children}</Items>
</>
);
};
Header.propTypes = {
children: PropTypes.object,
current: PropTypes.bool,
title: PropTypes.node
};
const Title = styled.div`
margin-right: 14px;
font-size: 20px;
border-bottom: 2px solid
${(props) => (props.current ? "#191919" : "transparent")}; // <-- use current prop
transition: border-bottom 0.5s ease-in-out;
`;
I'm building a typescript react app that has a child component called Accordion that when is clicked it is opened. When is opened it renders a table with some data. This accordion is made depending on a group that can be changed with a selector. My problem is that I want that when I change this group by my Accordion component closes if it's opened. I tried to pass a prop to close the Accordion but nothing occurs and I'm starting to be frustrated. How can I reload this component in order for the state to be closed? That's my code:
This is my Accordion component:
import React, { useState, useRef, Fragment, ReactChildren, ReactNode } from "react";
import Chevron from "./Chevron"
interface accordionPropsType {
title: string
children: ReactNode
}
const Accordion = (props: accordionPropsType) => {
const [setActive, setActiveState] = useState("");
const [setHeight, setHeightState] = useState("0px");
const [setRotate, setRotateState] = useState("accordion__icon");
const content = useRef(null);
const toggleAccordion = () => {
setActiveState(setActive === "" ? "active" : "");
setHeightState(setActive === "active" ? "0px" : `${content.current.scrollHeight}px`);
setRotateState(setActive === "active" ? "accordion__icon" : "accordion__icon rotate");
}
return(
<Fragment>
<div className="accordion__section">
<button className={`accordion ${setActive}`} onClick={toggleAccordion}>
<p className="accordion__title">{props.title}</p>
<Chevron className={`${setRotate}`} width={10} color={"#777"} />
</button>
<div
ref={content}
style={{ maxHeight: `${setHeight}` }}
className="accordion__content"
>
{props.children}
</div>
</div>
<style jsx>
{`
/* Style the accordion section */
.accordion__section {
display: flex;
flex-direction: column;
margin: 10px;
}
/* Style the buttons that are used to open and close the accordion panel */
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
display: flex;
align-items: center;
border: none;
outline: none;
transition: background-color 0.6s ease;
}
/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
.accordion:hover,
.active {
background-color: #ccc;
}
/* Style the accordion content title */
.accordion__title {
font-family: "Open Sans", sans-serif;
font-weight: 600;
font-size: 14px;
text-align: left;
}
/* Style the accordion content panel. Note: hidden by default */
.accordion__content {
background-color: white;
overflow: auto;
transition: max-height 0.6s ease;
margin: 5px;
}
/* Style the accordion content text */
.accordion__text {
font-family: "Open Sans", sans-serif;
font-weight: 400;
font-size: 14px;
padding: 18px;
}
`}
</style>
</Fragment>
);
}
export default Accordion;
And this is the component that calls this child component:
import React, { useState, Fragment, useEffect, FormEvent } from "react"
import Select from "../components/Select"
import Accordion from "../components/Accordion"
import Table from "../components/Table"
interface findingsType {
body: object
header: Array<headersType>
}
interface headersType {
className: string
rows: Array<rowType>
}
interface rowType {
className: string
rowspan: number
colspan: number
text: string
}
const CloudFindingsList = (props) => {
const [groupBy, setGroupBy] = useState<Array<string>>([]);
const [tableData, setTableData] = useState<findingsType>(null);
const headerRows = [] as Array<rowType>
const headers = [{
className: "thead_custom" as string,
rows: headerRows
}] as Array<headersType>
console.log('eee')
const getGroupBy = (event) => {
let distinctGroupsBy = []
let allFindings = {}
props.findings.map(finding => {
let value = finding[event.target.value]
distinctGroupsBy.includes(value) ? '' : distinctGroupsBy.push(value)
})
distinctGroupsBy.map(order => {
allFindings[order] = []
})
props.findings.map(finding => {
let value = finding[event.target.value]
distinctGroupsBy.map(order => {
value == order ? allFindings[order].push(finding) : ''
})
});
setGroupBy(distinctGroupsBy)
console.log(groupBy)
Object.keys(allFindings[distinctGroupsBy[0]][0]).map(value => {
headerRows.push({
className: "" as string,
rowspan: 0 as number,
colspan: 0 as number,
text: value as string
})
})
setTableData({
header: headers,
body: allFindings
} as findingsType)
}
const listFindings =
groupBy.map((group, index) => {
return(
<Accordion title={group} key={index}>
<Table jsonData={tableData.body[group]} header={tableData.header}/>
</Accordion>
)
})
return(
<Fragment>
<Select
id='1'
options={[{"val": "severity", "text": "severity"}, {"val": "account", "text": "account"}, {"val": "service", "text": "service"}]}
placeholder='Group by'
handleChange={getGroupBy as () => {}}
/>
{listFindings}
</Fragment>
);
}
export default CloudFindingsList
You don't have to understand all the code I just want that when I change the selected item in the selector the Accordion is closed again. Does anyone see the solution?
Thanks!
You could try to use a useEffect hook passing the props of the Accordion as change parameter. Every time that prop change you execute the code that changes the value.
useEffect(() => {
// YOUR CODE GOES HERE
}, [props])
I am using Framer Motion as an animation library in React project. I am trying to animate parent element after child element using when attribute. It doesn't work, because ContentVariants and ImgVariants are running simultaneously.
codesandbox
import React, { Component } from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
import { motion } from "framer-motion";
export const ContentVariants = {
expanded: () => ({
width: "150px",
transition: {
when: "afterChildren",
duration: 2
}
}),
collapsed: () => ({
width: "50px",
transition: {
when: "afterChildren",
duration: 2
}
})
};
export const Content = styled(motion.div)`
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
background-color: burlywood;
padding: 30px;
height: 500px;
`;
export const ToggleBtn = styled.button`
padding: 5px 10px;
cursor: pointer;
display: flex;
width: auto;
align-self: flex-end;
`;
export const ImgVariants = {
expanded: {
width: "100px",
scale: 1,
transition: {
duration: 2
}
},
collapsed: {
scale: 0.5,
transition: {
duration: 2
}
}
};
const Img = styled(motion.img)``;
class App extends Component {
state = {
collapsed: false
};
toggle = () => {
this.setState({ collapsed: !this.state.collapsed });
};
render() {
const { collapsed } = this.state;
return (
<div>
<Content
initial={collapsed ? "collapsed" : "expanded"}
animate={collapsed ? "collapsed" : "expanded"}
variants={ContentVariants}
>
<Img
src="https://picsum.photos/200/200"
initial={collapsed ? "collapsed" : "expanded"}
animate={collapsed ? "collapsed" : "expanded"}
variants={ImgVariants}
/>
<ToggleBtn onClick={this.toggle}>toggle</ToggleBtn>
</Content>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
If I change when: "afterChildren" to when: "beforeChildren" in ContentVariants, it doesn't do any difference. Even if I remove when attribute, animations are running simultaneously.
Documentation propagation section (https://www.framer.com/api/motion/animation/#propagation) says that
If a motion component has children, changes in variant will flow down
through the component hierarchy. These changes in variant will flow
down until a child component defines its own animate property.
You have to remove the animate prop from Img elements.
https://codesandbox.io/s/gallant-goldwasser-dwiz2
If you set an animation on your child, your parent won't pass it's animation logic to it. Thus you have to remove the initial and animate property from you <Img> component:
<Img
src="https://picsum.photos/200/200"
variants={ImgVariants}
/>
You can look at this example from the official docs for a reference: https://www.framer.com/api/motion/types/#orchestration.when