redux: Component not updating in React Native

I’ve followed the tutorial so far and I have to admit that, albeit the Flux/Redux architecture has a steep learning curve, I managed to successfully make almost everything work very quickly and understanding all the fundamental things.

However, the component I’m rendering is not updating when an action is dispatched.

I want to add elements to a list. Here’s the code I made so far:

  • Actions.js:
export const ADD_ELEMENT = 'ADD_ELEMENT'
export function addElement (element) {
  return { type: ADD_ELEMENT, element }
}
  • Reducers.js:
import { ADD_ELEMENT } from 'Actions'
import { combineReducers } from 'redux'

const initialState = {
  elements: [
    {id: 1, element: 'thing'},
    {id: 2, element: 'anotherThing'}
  ],
}

function elements (state = initialState.elements, action) {
  switch (action.type) {
    case ADD_ELEMENT:
      return [...state, action.element]
    default:
      return state
  }
}

const boatApp = combineReducers({
  elements
})

export default boatApp
  • index.ios.js:
// ...Imports
const logger = createLogger()
const createStoreWithMiddleware = applyMiddleware(thunk, promise, logger)(createStore)
const store = createStoreWithMiddleware(boatApp)

class boat extends Component {
  render () {
    return (
      <Provider store={store}>
        {() => <TabBar />}
      </Provider>
    )
  }
}

Then, inside the <TabBar /> component I have a view with a React Native <ListView /> component that has to display the list of elements:

import React, { Component, View, Text, ListView, StyleSheet } from 'react-native'
import { connect } from 'react-redux/native'

@connect(state => ({ elements: state.elements }))
class ElementItemsList extends Component {
  constructor (props) {
    super(props)
    let dataSource = new ListView.DataSource({
      rowHasChanged: (r1, r2) => r1 !== r2
    })
    this.state = {
      elements: props.elements,
      dataSource: dataSource.cloneWithRows(props.elements)
    }
  }

  _renderRow (rowData) {
    return (<Text>{rowData.element}</Text>)
  }

  render () {
    return (
      <View style={{flex: 1}}>
        <ListView style={styles.container}
          contentInset={{top: 0}}
          automaticallyAdjustContentInsets={false}
          dataSource={this.state.dataSource}
          renderRow={(rowData) => this._renderRow(rowData)}
        />
        <Text style={{flex: 1}}>
          {this.state.elements}
        </Text>
      </View>
    )
  }
}

The thing is that I need to update this.state.dataSource in order to make <ListView /> update it’s content. Any clue about how I can update the state when the elements are updated?

Update Just FYI, there is also a <Text /> component I put after the ListView that doesn’t need the dataSource update, it prints this.state.elements and it’s not updating also.

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 23 (5 by maintainers)

Commits related to this issue

Most upvoted comments

Alas, I don’t know enough about what dataSource is to help you. The problem is not Redux-specific, and is described in Props in getInitialState is an anti-pattern. You’ll get new props from Redux every time store updates, but you don’t use them: you only use state which you initialize once.

Does something like this help?

componentWillReceiveProps (nextProps) {
  if (nextProps.elements !== this.props.elements) {
    this.setState({
      elements: nextProps.elements,
      dataSource: this.state.dataSource.cloneWithRows(nextProps.elements)
    })
  }
}

Note that unlike componentDidUpdate(), a setState() call inside componentWillReceiveProps() won’t trigger another render, so it will be faster.

Still, it looks rather weird to me, but again, I don’t know enough about React Native or dataSource.

I ended up doing something like this.

import React, {ListView, PropTypes, Component, View, Text} from 'react-native';
import {connect} from 'react-redux/native';

class TodoList extends Component {
  renderRow(todo) {
    return (
      <View><Text>{todo.text}</Text></View>
    );
  }

  render() {
    return (
      <ListView
        dataSource={this.props.dataSource}
        renderRow={(rowData) => this.renderRow(rowData)}
      />
    );
  }
}

TodoList.propTypes = {
  dataSource: PropTypes.object,
};

const dataSource = new ListView.DataSource({
  rowHasChanged: (r1, r2) => r1 !== r2,
});

function mapStateToProps(state) {
  return {
    dataSource: dataSource.cloneWithRows(state.todos),
  };
}

export default connect(mapStateToProps)(TodoList);

Couldn’t you just create a new DataSource instance in the render method? or do something like this:

@connect(state => ({ elements: state.elements }))
class ElementItemsList extends Component {
  constructor (props) {
    super(props)
    this.dataSource = new ListView.DataSource({
      rowHasChanged: (r1, r2) => r1 !== r2
    })
  }

  _renderRow (rowData) {
    return (<Text>{rowData.element}</Text>)
  }

  render () {
    const dataSource = this.dataSource.cloneWithRows(this.props.elements);

    return (
      <View style={{flex: 1}}>
        <ListView style={styles.container}
                  contentInset={{top: 0}}
                  automaticallyAdjustContentInsets={false}
                  dataSource={dataSource}
                  renderRow={(rowData) => this._renderRow(rowData)}
        />
        <Text style={{flex: 1}}>
          {this.state.elements}
        </Text>
      </View>
    )
  }
}

Or does that result in performance issues?

(Disclaimer: I don’t know a lot about React Native)

(Small suggestion: elements is confusing naming because I thought you’re referring to React elements, i.e. stuff you get from React.createElement. Better to rename it to items.)

Instead of setting an attribute against the row, maintain a “selected_row” key: {selected_row: row_id}

@sompylasar @luqmaan @alvaromb This solution didn’t work for me,i’m using react native’s Navigator component to render my scenes, could this be the cause, although when JavaScript Set interval to console log the same prop data it displays the new state when changed, can’t seem to tell whats wrong here.

This is my component’s class:

class Header extends Component{

	constructor(props){ ... }

	setOptions(){ ... }

	navbar(){
		this.props.showNavbar();
	}
	componentDidMount() {
		setInterval(() => {
			console.log(this.props.navbar);
		}, 5000)
	}
	componentWillReceiveProps(nextProps) {
               // This does not fire
		console.log(nextProps);
	}
	componentWillMount() {
		// set options
		this.setOptions();
	}
	render(){
		return(
			<View style={styles.conatiner}>
                               ...
			</View>
		);
	}
}

Someone please help!

I moved

  const dataSource = new ListView.DataSource({
    rowHasChanged: (r1, r2) => r1 !== r2,
  });

outside the function. Works fine for me. The problem I’m having is if I want to press on the row and make it active/checked, I have to go through the whole list in the reducer find the row and change the state on it, doesn’t seem to be very efficient… Any opinion on how or where properly to set selected: true/false on the row?