How can we animate the display of records using react-spring
. Can we call the useTransition
in useEffect() ? I would like to animate records from bottom to top one by one when user navigate to the page. I have tried below nothing is happening..? Any advise would be really helpful.
https://codesandbox.io/s/inspiring-snowflake-34dpx?file=/src/App.js:711-881
import { useEffect, useState } from "react";
import { useTransition, animated } from "react-spring";
import "./styles.css";
const array1 = [
{ id: "1", name: "Cat" },
{ id: "2", name: "Dog" },
{ id: "3", name: "Cow" }
];
export default function App() {
const [list, setList] = useState(array1);
const height = 20;
useEffect(() => {
setList(array1);
}, []);
const transitions = useTransition(
Object.entries(list).map((data, i) => ({ ...data, y: i * height })),
{
from: { position: "absolute", opacity: 0 },
enter: { height: 0, opacity: 1 },
leave: { opacity: 0 },
config: { tension: 220, friction: 120 }
}
);
return (
<div className="App">
{/* {Object.entries(list).map((item, index) => (
<div key={item.id} className="record">
<span key={item.name}>{item.name}</span>
</div>
))} */}
{Object.entries(transitions).map(({ item, props, key }) => (
<animated.div key={key} style={{ ...props, position: "absolute" }}>
<div key={item.id} className="record">
<span key={item.name}>{item.name}</span>
</div>
</animated.div>
))}
</div>
);
}
The transitions
variable is a function, so you don't want Object.entries(transitions).map()
. It's not an error because functions in Javascript are also objects, but it's not going to do anything because the array of entries is an empty array.
You want to call the transitions
function, which is its own sort of mapper.
{transitions((props, item, key) => (
Well now I'm seeing a cyan box instead of a white screen. So that's a little progress, but there's more to do.
If you console.log
your item
object you'll see that it doesn't look right.
Object.entries(list).map((data, i) => ({ ...data, y: i * height }))
You are mapping an array of object entries, so the data
variable is an array containing the key and the value. You don't want that. list
is already an array, so ditch the Object.entries
.
list.map((data, i) => ({ ...data, y: i * height })),
Now I see "Cow"! But the other two are hidden because your items are appearing on top of each other.
You are using absolute positioning but you haven't passed the position down to the component. Since you aren't animating that value, it's a property of item
rather than props
.
<animated.div key={key} style={{ ...props, position: "absolute", top: item.y }}>
Now I see all three!
But the overlapping background blocks are probably not what you intended. I'm not sure what you intended, really, because you have a height
of 20
in your JS and 100px
in your CSS. You also have a height
property in your animation but it's not doing anything.
All of your list items have height: 0px
because you are setting the height to 0
in the enter
property, which is called when an item first enters the list. On the other hand, the from
property sets the initial values that the item animates away from, so maybe you want to have an initial value of height: 0
in from
and some final value of height
in enter
.
You can animate the height of the background, but that won't impact the height of the text. You might want a transform
like scaleY
. You can use a transform instead of the height animation (all items start out in their final position and grow in place) or alongside the height animation (all items start together at the top and grow down).
Here's one possible effect: Code Sandbox Link
style.css
.App {
font-family: sans-serif;
text-align: center;
}
.record {
width: 300px;
background-color: aqua;
margin: 20px 0;
color: #1f1d1d;
}
.title {
font-size: 30px;
padding: 35px;
}
App.js
import { useState } from "react";
import { useTransition, animated } from "react-spring";
import "./styles.css";
const array1 = [
{ id: "1", name: "Cat" },
{ id: "2", name: "Dog" },
{ id: "3", name: "Cow" }
];
export default function App() {
const [list, setList] = useState(array1);
const transitions = useTransition(list, {
from: { opacity: 0, scaleY: 0 },
enter: { opacity: 1, scaleY: 1 },
leave: { opacity: 0 },
config: { tension: 220, friction: 120 }
});
return (
<div className="App">
{transitions((props, item, key) => {
console.log(props, item, key);
return (
<animated.div key={key} style={{ ...props }} className="record">
<div className="title">{item.name}</div>
</animated.div>
);
})}
</div>
);
}
Edit
As requested, from the bottom to the top one after another:
export default function App() {
const height = window.innerHeight;
const [list, setList] = useState(array1);
const transitions = useTransition(list, {
from: { opacity: 0, translateY: height },
enter: { opacity: 1, translateY: 0 },
leave: { opacity: 0 },
config: { tension: 220, friction: 120 },
trail: 200
});
return (
<div className="App">
{transitions((props, item, key) => {
console.log(props, item, key);
return (
<animated.div key={key} style={{ ...props }} className="record">
<div className="title">{item.name}</div>
</animated.div>
);
})}
</div>
);
}