Is it impossible to use Framer Motion on instant Search Results? Animating an updating .map() function - reactjs

https://i.imgur.com/drQmJn5.mp4
The initial results are animated perfectly. Staggered fade-ins.
However, all results AFTER that just appear suddenly and without animation. I'm not understanding why the updated results aren't also animated.
The code currently looks like this:
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.3
}
}
};
const listItem = {
hidden: { opacity: 0},
show: { opacity: 1}
};
<PostList
variants={container}
initial="hidden"
animate="show"
as={motion.ul}>
{results && results.slice(0, 2).map((result, i) => (
<PostListItem
as={motion.li}
variants={listItem}
key={i}
> blah blah
</PostListItem>
))}

You should not use the loop index as the key in your PostListItem. Since the indices (and thus the keys) will always be the same, it doesn't let Framer track when elements have been added or removed in order to animate them.
Instead use a value like a unique id property for each element. This way React (and Framer) will know when it's rendering a different element (vs the same element with different data). This is what triggers the animations.
Here's a more thorough explanation:
react key props and why you shouldn’t be using index

Related

Set value immediately after another one have been set in Reanimated 2

I am using Reanimated 2 to build a game with React Native its performance is incredibly good but I have a problem.
I am using a shared value to animate a View as we all know setting the value of the shared value will automatically change the style of the View, my problem is that let's say it will be a Button that the user presses to give the View an elevation simply by changing a shared value used in the animated style of the View, the elevation is simply translation in the y axis.
The elevation value is 0 at first. The user clicks the button the value changes to for example 500 immediately with no transition and no animation, the View will immediately show at 500 above its starting position. And from 500 the View will drop back to 0 with animation.
I tried the code below but no help.
const elevation = useSharedValue(0);
const handleClick = () => {
elevation.value = 500;
elevation.value = withTiming(0, { duration: 1000 });
}
const viewAnimatedStyle = useAnimatedStyle(() => ({
transform: [
{
translateY: elevation.value,
}
]
}))
when pressing the button the view doesn't move, it seems that Reanimated skips the first elevation.value assignment, and since the second assignment is to 0 (the same old value) the View doesn't move.
[Edit] Animated.View is imported from Reanimated 2 and used. I left it out for simplicity.
You need to use <Animated.View> for the useAnimatedStyle, If you are now using It will not be working correctly.
function App() {
const width = useSharedValue(50);
const animatedStyle = useAnimatedStyle(() => {
return {
width: width.value,
};
});
// attach animated style to a View using style property
return <Animated.View style={[styles.box, animatedStyle]} />;
}

Intersection Observer loses entries

I am trying to use IntersectionObserver in my react app. I want to achieve lazy loading with it. Therefore I observe several elements and if they appear on screen I load content inside them.
Here is a very simplified code:
class Table extends React.Component {
constructor() {
super()
this.state = {
entries: 0
}
}
componentWillUnmount() {
console.log('componentWillUnmount')
if (this.observer) this.observer.disconnect()
}
observe (c) {
if (!this.observer) {
this.observer = new IntersectionObserver(
entries => {
this.setState({entries: entries.length})
},
{ threshold: [0, 0.25, 0.5, 0.75, 1] }
)
}
if (!c) return
this.observer.observe(c)
}
render() {
const {entries} = this.state
return (
<div>
<h1>Number of observer entries: {entries}</h1>
<div
ref={this.observe.bind(this)}
style={{height: '1000px', display: 'block', width: '500px'}}
/>
<div
ref={this.observe.bind(this)}
style={{height: '1000px', display: 'block', width: '500px'}}
/>
</div>
)
}
}
ReactDOM.render(<Table />, document.querySelector("#app"))
When a component is mounted it shows two elements are observed but as soon as I scroll down it changes to only one element. I have not idea what I am missing.
JSON fiddle - https://jsfiddle.net/w1zn49q6/12/
The divs are stacked one after the other vertically. In the initial render, as they are laid out, the intersection observer gets triggered for both, as they enter the viewport together (the first div enters, the second div exits). However, once they are rendered, they will enter/exit one at a time on a normal course of vertical scrolling, hence the entries will only ever contain one div, which intersected the x-axis most recently.
The intersection entry only reports a transition to/from 0 (not in view) from/to 1 (fully in view). So when one div has fully exited/entered the view, it will no longer be present in an entry update.
You can still get 2 entries however :). If you manage to scroll really fast! Try it using an accelerated mouse wheel. So basically, between two intersection calculations, if both the divs moved too far, both will raise the intersection event, but if they move slowly, the intersection will be gradual because they are stacked one by one.
If you would stack them in the same row, you will continuously get two entries, as both will be intersecting at the same moment with the x-axis.

how to animate text when it updates using react-spring?

in a react.js App,
i have some simple text stored in the App state , and passed to a child component who will display it directly .
i want to animate the transition so that when it changes , the previous text will fade out and then the incoming text will fade in .
how is that possible using react-spring ?
You can put it into a transition and it will handle it for you:
const transitions = useTransition(text, null, {
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 }
})
return transitions.map(({ item, key, props }) => (
<animated.div style={props}>{item}</animated.div>
))
Make sure the div is positioned absolutely if you want text phrases to overlap one another.

Component is not re rendering after state change in react

While working on my project I faced this issue and tried for two days to solve it. Finally I decided to post it here.
I am rendering array by calling child component as follows:-
Parent component
{this.state.list.map((item,index)=>{
return <RenderList
insert={this.insert.bind(this,index)}
index={index}
length={this.state.list.length}
EnterPressed={this.childEnterPressed.bind(this,index)}
list={item}
onchange={this.childChange.bind(this)}
editable={this.state.editable[index]}
key={index}
onclick={this.tagClicked.bind(this,index)} />
})}
and
Child component
<Draggable grid={[37, 37]}
onStop={this.handleDrop.bind(this)}
axis="y"
onDrag={this.handleDrag.bind(this)}
bounds={{top: -100, left: 0, right: 0, bottom: 100}}
>
<div onClick={this.tagClicked.bind(this)}>
{ this.props.editable ? (<Input
type="text"
label="Edit Item"
id="listItem"
name="listItem"
onKeyPress={this.handleKeyPress.bind(this)}
defaultValue={this.props.list}
onChange={this.onChange.bind(this)} />) :
(<Chip>{this.props.list}</Chip>) }
</div>
</Draggable>
I am using react-draggble to make each individual item of my list draggble. When ot is dragged in handleDrag function I am just updating my state of distance by which component is draged and in handleDrop function I am calling insert function which delete the dragged item from its current position and insert at position where it is dropped.
insert=function(index,y,e){
if(y!==0){
var list=this.state.list;
var editable = Array(this.state.editable.length).fill(false);
if(y/37>0){
var temp=list[index];
var i=0;
for(i=index;i<index+y/37;i++){
list[i]=this.state.list[i+1];
}
list[(y/37)+index]=temp;
this.setState({
...this.state,
list:list,
editable:editable
})
}
}
State is updating correctly. I have tested it but component is not re rendering. what surprised me is that if I just display the list in the same component then it re render correctly.any help is appreciated!
React cannot keep track of the items, as your are using the index as key (which is discouraged). Try to create a unique key based on something like a generated uuid which sticks to the item.
When dragging an item, the position (index) in the array changes. This prevents React from syncing the correct elements. Prop/state changes may not propagate as wanted.

Why is React's DOM reconciliation not working as expected?

I'm trying to swap two children of an element with a transition using React.
<div style={{position: 'relative'}}>
{this.state.items.map((item, index) => (
<div
key={item}
style={{position: 'absolute',
transform: `translateY(${index * 20}px)`,
transition: '1s linear transform'}}>
{item}
</div>
))}
</div>
state.items is an array of two items. When it is reordered, the two child divs should transition to their new positions.
What happens in reality is while the second element transitions as expected, the first one jumps instantly.
As far as I can tell, React thinks that it can reuse one of the child elements, but not the other, although the docs say that if we use the key attribute, it should always reuse elements: https://facebook.github.io/react/docs/reconciliation.html (at least, that's how I understand it).
What should I change in my code to make it work as expected? Or is it a bug in React?
Live example: http://codepen.io/pavelp/pen/jAkoAG
caveat: I'm making some assumptions in this answer, nevertheless it shines light some of your (and previously my) questions. Also my solution can almost certainly be simplified, but for the purposes of answering this question it should be adequate.
This is a great question. I was a bit surprised to open up the dev tools and see what's actually happening when swapping the items.
If you take a look, you can sort of see what React is up to. The second element is not changing its style prop at all and just swaps the inner text node, while the first element is dropped into the dom as a fresh element.
If I had to guess, this is because of the way swapping two items in a array works, where at least one item is copied to a temp variable and placed back into the array.
I thought that maybe if you make the translation random, both elements would get new style props and animate, but that only made it more clear this was not the intended behaviour.
On the way to finding a solution:
As an experiment, what if we created the nodes ahead of time, and pass the index prop in render via React.cloneElement. While we're at it, let's render a span if index === 0 and a div otherwise. No keys to worry about.
http://codepen.io/alex-wilmer/pen/pbaXzQ?editors=1010
Opening up the dev tools now illustrates exactly what React is intending. React is preserving the elements and only changing the relevant part, in this case the innerText node and the element type. Because the styles are swapped exactly 1 : 1, no style update is needed.
Solution:
You can generate your React elements ahead of time, keep those in an array, and as such there are no keys to shuffle around and figure out how to place back into the DOM. Then use a different array to keep track of the intended order. Possibly highly convoluted, but it works!
http://codepen.io/alex-wilmer/pen/kXZKoN?editors=1010
const Item = function (props) {
return (
<div
style={{position: 'absolute',
transform: `translateY(${props.index * 20}px)`,
transition: '0.5s linear transform'}}>
{props.children}
</div>
)
}
const App = React.createClass({
getInitialState () {
return {
items: [
{item: 'One', C: <Item>One</Item>},
{item: 'Two', C: <Item>Two</Item>}
],
order: ['One', 'Two']
};
},
swap () {
this.setState({
order: [this.state.order[1], this.state.order[0]]
});
},
render: function () {
return <div>
<button onClick={this.swap}>Swap</button>
<div style={{position: 'relative'}}>
{this.state.items.map(x =>
React.cloneElement(x.C, {
index: this.state.order.findIndex(z => z === x.item)
}))
}
</div>
</div>;
}
});

Resources