Skip to content

Modus-Logo-Long-BlackCreated with Sketch.

  • Services
  • Work
  • Blog
  • Resources

    OUR RESOURCES

    Innovation Podcast

    Explore transformative innovation with industry leaders.

    Guides & Playbooks

    Implement leading digital innovation with our strategic guides.

    Practical guide to building an effective AI strategy
  • Who we are

    Our story

    Learn about our values, vision, and commitment to client success.

    Open Source

    Discover how we contribute to and benefit from the global open source ecosystem.

    Careers

    Join our dynamic team and shape the future of digital transformation.

    How we built our unique culture
  • Let's talk
  • EN
  • FR

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:

React Native Dynamic Animated Lists examples

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

Let’s try to add an item by creating a fade-in animation.

Consider the following component that contains a ListView that will load initial data from a JSON file. We want to add more items to its data source and animate that.

This is the default component with the render() method:

export default class DynamicList extends Component {

   state = {
       loading     : true,
       dataSource  : new ListView.DataSource({
           rowHasChanged : (row1, row2) => true
       }),
       refreshing  : false
   };
   // … omitting parts of the code to keep it short and have the main methods only
   render() {
       return (
           <View style={styles.container}>
                 <ListView
                   dataSource={this.state.dataSource}
                   renderRow={this._renderRow.bind(this)}
               />
           </View>
       );
   }

Here is the renderRow() method for the list view

   _renderRow(rowData, sectionID, rowID) {
       return (
           <DynamicListRow>
                … Views defined in between ...
           </DynamicListRow>
       );
   }

}

Now, let’s define the DynamicListRow component and see what we need to have in order to implement an animated added list item:

class DynamicListRow extends Component {

   _defaultTransition  = 250;

   state = {
       _rowOpacity : new Animated.Value(0)
   };

   componentDidMount() {
       Animated.timing(this.state._rowOpacity, {
           toValue  : 1,
           duration : this._defaultTransition
       }).start()
   }

   render() {
       return (
           <Animated.View
               style={{opacity: this.state._rowOpacity}}>
               {this.props.children}
           </Animated.View>
       );
   }

}

As you can see here, we have defined the initial row opacity value in the state. When the component is mounted, componentDidMount is fired as well as the animation using the Animated React Native component.

So far so good. Cheers! We have succeeded in adding an item and animating this process.

  • Animate the Removal Process

Let’s say we want to remove an item and we want to animate the height. This is the most common behavior used for removing items from a ListView.

This is a little more complicated than adding an item to the list. If you add the same animation in the same way as on componentDidMount(for addition), on componentWillUnmount(for removal) this will not work. This is because ListView removes the list item and the animation does not have time to take place. Animation is an asynchronous activity, so when the dataSource updates, the item is cleared from the list directly.

How can we work around this?

Here’s some code and comments to explain the whole process.

export default class DynamicList extends Component {
   /* Default state values */
   state = {
       loading     : true,
       dataSource  : new ListView.DataSource({
           rowHasChanged : (row1, row2) => true /*  It is important for this to be 
always true so that the renderRow fires every time we update the dataSource and we set 
the state without needing to have different items added/removed or updated to the list 
view */
       }),
       refreshing  : false,
       rowToDelete : null /* this will keep the id of the item to track if will need 
to be animated and removed afterwards */
   };
/* Loading data after interactions are done will ensure that the transitions and other 
activity regarding view rendering has already happened and this is important especially 
when this is the first View that renders */

   componentDidMount() {
       InteractionManager.runAfterInteractions(() =>  this._loadData() ); 
   }
/* … other methods … */

/* 
Render a simple ListView. Normally at this point you would have to check on loading 
state and display a spinner while data loads async from the server, but in this case 
is not needed as we’re loading everything from a simple local JSON file.
I have bound the scope of this component to _renderRow method to keep it in this 
component’s scope and avoid switching the scope to ListView as renderRow is an internal 
property in ListView.
 */
render() {
       return (
           
               <ListView
                    dataSource={this.state.dataSource}
                   renderRow={this._renderRow.bind(this)}
               />
       );
   }
/*
For rendering the list rows we will use DynamicListRow component. 
The remove property will be responsible for firing the collapse animation of the 
removal process within DynamicListRow component.
onRemoving  is going to be fired by the component when the animation transition ends 
and we will attach _onAfterRemovingElement() which is bound in the current scope as well.
 */
_renderRow(rowData, sectionID, rowID) {
   return (
       <DynamicListRow
           remove={rowData.id === this.state.rowToDelete}
           onRemoving={this._onAfterRemovingElement.bind(this)}
       >
         // …. Nested items ...
       </DynamicListRow>
   );
}




/* deleteItem() only sets the property rowToDelete on the state to distinguish the 
one that has to be deleted */
_deleteItem(id) {
   this.setState({
       rowToDelete : id
   });
}

/* Setting the state will fire the re-rendering process and componentWillUpdate will 
fire as well. This function is called on the callback of the animation so that it 
only updates the dataSource with the cached data when the delete animation is done */
_onAfterRemovingElement() {
   this.setState({
       rowToDelete : null,
       dataSource  : this.state.dataSource.cloneWithRows(this._data)
   });
}
/* When the state is changed componentWillUpdate fires and will update the cached 
data we need to render on the list view */
componentWillUpdate(nextProps, nextState) {
   if (nextState.rowToDelete !== null) {
       this._data = this._data.filter((item) => {
           if (item.id !== nextState.rowToDelete) {
               return item;
           }
       });
   }
}

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.

Posted in Application Development
Share this

Alex Lazar

Alex Lazar was a Senior Architect at Modus Create and Head of Modus Create Romania. He is passionate about emerging technologies and has extensive experience in frontend and backend development. He has built several desktop web, mobile-web, hybrid, React Native and native apps. Alex is currently based in Cluj Napoca, Romania and is the organizer of the Cluj.JS Meetup group. When he’s away from the keyboard, he likes to ride his bike, practice martial arts, and play with his daughter.
Follow

Related Posts

  • React Navigation and Redux in React Native Applications
    React Navigation and Redux in React Native Applications

    In React Native, the question of “how am I going to navigate from one screen…

  • Using ES2016 Decorators in React Native
    Using ES2016 Decorators in React Native

    *picture courtesy of pixabayDecorators are a popular bit of functionality currently in Stage 1 of…

Want more insights to fuel your innovation efforts?

Sign up to receive our monthly newsletter and exclusive content about digital transformation and product development.

What we do

Our services
AI and data
Product development
Design and UX
Modernization
Platform and MLOps
Developer experience
Security

Our partners
Atlassian
AWS
GitHub
Other partners

Who we are

Our story
Careers
Open source

Our work

Our case studies

Our resources

Blog
Innovation podcast
Guides & playbooks

Connect with us

Get monthly insights on AI adoption

© 2025 Modus Create, LLC

Privacy PolicySitemap
Scroll To Top
  • Services
  • Work
  • Blog
  • Resources
    • Innovation Podcast
    • Guides & Playbooks
  • Who we are
    • Our story
    • Careers
  • Let’s talk
  • EN
  • FR