0

I'm trying to animate the swapping of two rows with a React Native ListView

Here's my datasource code (both in my render method):

const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1.id !== r2.id})
var dataSource = ds.cloneWithRows(state.todoLists[this.props.index].todos.slice())

Here's my swap code (using MobX):

    var {todo, i} = forID(state.todoLists[list].todos, id) //Gets index and todo from  id
    state.todoLists[list].todos.splice(i, 1)
    state.todoLists[list].todos.push({...todo, done: !todo.done})

As you can see, the equivalence is persisted since the array item is just shifted down.

If I try LayoutAnimation, I get this weirdness: enter image description here

Any ideas?

Mark Estefanos
  • 493
  • 1
  • 4
  • 12

1 Answers1

1

I suspect the problem is the fact that (according to what you say) you are creating the DataSource in your render method. You should create the ListView.DataSource object in your constructor (or componentWillMount), then call cloneWithRows when your data changes, not in render. The issue is that by recreating a new DataSource on each render, it never calls the rowHasChanged function because there is never a previous state in the datasource.

Example Correct Implementation

In the below example, I setup the datasource in the constructor and store it in the state. Then once mounted, I have it load the todos, and update the datasource in the state, which will trigger a re-render.

Then, when you want to move a todo to the bottom, you would call this.moveItemToBottom(id), which modifies the state, and updates the datasource on the state, and re-render after setting up the LayoutAnimation.

class TodoList extends Component {
  constructor(props, context) {
    super(props, context);

    this.state = {
      ds: new ListView.DataSource({
        rowHasChanged: (r1, r2) => (r1 !== r2),
      }),
    }; 
  }

  componentDidMount() {
    loadInitialData();
  }
  
  loadInitialData() {
    // Do something to get the initial list data
    let todos = someFuncThatLoadsTodos();

    this.setState({
      ds: this.state.ds.cloneWithRows(todos),
    });
  }

  moveItemToBottom(id) {
    // get your state somewhere??
    let todos = state.todoLists[list].todos;

    let {todo, i} = forID(state.todoLists[list].todos, id)
    todos.splice(i, 1).push({...todo, done: !todo.done});
    
    LayoutAnimation.easeInEaseOut();
    this.setState({
      ds: this.state.ds.cloneWithRows(todos),
    });
  }
  
  render() {
    return (
      <ListView
        dataSource={this.ds}
        // Other props
      />
    );
  }
}

EDIT/NOTE: My example doesn't take into account anything to do with MobX. I haven't used it, but from a cursory look, you may need to observe the todos list and update the datasource whenever it updates, and just have the moveItemToBottom method update the MobX state and rely on the observable to setState with the cloned datasource.

Dan Horrigan
  • 1,275
  • 7
  • 8