Dynamic Animated ListView in ReactNative
Would you like to add some visual sugar to your dynamic lists in React Native? Do you want to create a pleasant visual experience for users of your application? Here’s a tutorial that will give you a step by step approach to creating your own dynamic animated ListView in React Native.
Here’s an animated gif to illustrate what I mean by an animated ListView:
Every time you want to add or remove something from your list to have the option to apply an animation transition to your elements.
Why could it be more complicated than you initially think in React Native?
The ListView Data Source needs to be updated in order to display the new data for whatever operations you want to perform on it. The problem is that the ListView RN component doesn’t give you the ability to animate this process; it just happens and the data re-renders in the blink of an eye.
You can check the complete source code in our Github repo and run this demo. Read on as we take a look at how it works.
Animating the ListView: Adding and Removing Items
- Animate the Add Process
- Animate the Removal Process
The questions now are, why have we updated the cached data on componentWillUpdate and why do we need a separate function to actually update the dataSource value? The answer is, because we only need _deleteItem() to simulate the removal process of an item by firing out the height animation on the actual list row component.
Here’s how the list row component looks now:
class DynamicListRow extends Component {
// these values will need to be fixed either within the component or sent through props
_defaultHeightValue = 60;
_defaultTransition = 500;
state = {
_rowHeight : new Animated.Value(this._defaultHeightValue),
_rowOpacity : new Animated.Value(0)
};
componentDidMount() {
Animated.timing(this.state._rowOpacity, {
toValue : 1,
duration : this._defaultTransition
}).start()
}
componentWillReceiveProps(nextProps) {
if (nextProps.remove) {
this.onRemoving(nextProps.onRemoving);
} else {
// we need this for iOS because iOS does not reset list row style properties
this.resetHeight()
}
}
onRemoving(callback) {
Animated.timing(this.state._rowHeight, {
toValue : 0,
duration : this._defaultTransition
}).start(callback);
}
resetHeight() {
Animated.timing(this.state._rowHeight, {
toValue : this._defaultHeightValue,
duration : 0
}).start();
}
render() {
return (
<Animated.View
style={{height: this.state._rowHeight, opacity: this.state._rowOpacity}}>
{this.props.children}
</Animated.View>
);
}
}
On the componentWillReceiveProps() method, we’re checking if the remove property is set as true from the parent component in renderRow. This means the animation should be fired, but we also need to know when the animation ends so that the listview dataSource gets updated and we’re not getting invisible rows inside the list.
On iOS, the list rows that are hidden do not reset on ListView re-rendering process. We need to do this manually. Otherwise, we can see more items disappearing from the list and we do not know why. In fact, they are not deleted at all but set as hidden with the height 0.
These are the 3 methods that are also listed higher in the component and are responsible for handling all that.
/*
This method will fire when the component receives new props or the props change on the
component, if you check the renderRow method, you will see that there’s a remove
property that is changing every time the state changes
( remove={rowData.id === this.state.rowToDelete} )
*/
componentWillReceiveProps(nextProps) {
if (nextProps.remove) {
this.onRemoving(nextProps.onRemoving);
} else {
this.resetHeight()
}
}/*
Will animate the removal process, transitioning the height
*/
onRemoving(callback) {
Animated.timing(this.state._rowHeight, {
toValue : 0,
duration : this._defaultTransition
}).start(callback);
}
/*
Will reset the row height to the initial value. We need this because when we remove a row,
its height goes to 0 and when we actually remove it from the dataSource right after and
will fire re-rendering process, the row component does not “reset” on iOS, only the data
will change within rows, so we end up by seeing 2 fields go out if we don’t call this.
*/
resetHeight() {
Animated.timing(this.state._rowHeight, {
toValue : this._defaultHeightValue,
duration : 0
}).start();
}
And there you have it, a dynamic animated listview!
Final thoughts
In my experience, React Native has proven to be the perfect tool to achieve great native performance cross platform by just writing Javascript and JSX.
Indeed, things can get a little complicated when it comes to designing more complex components. Understanding the React component lifecycles and native rendering process are crucial to designing more complex components. As you dive deeper into it, you’ll find out how easy can it be to achieve what you want.
It is also true that you may have to deal with native code at some point, but that does not make much difference between React Native and Hybrid, because in hybrid development you have the native plugins as well to handle what you need. Creating native modules with bridges for React Native does not differ much from creating native plugins for Cordova, which is a topic worthy of a separate post altogether.