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.
And this is the output of the logger:

The only way to reflect the changes is to navigate to the previous component and navigate again to the list (forcing the state refresh).
I managed to update the current state but honestly I don't think it's the right way to do it:
componentDidUpdate () {
if (this.state.elements.length !== this.props.elements.length) {
this.setState({
elements: this.props.elements,
dataSource: this.state.dataSource.cloneWithRows(this.props.elements)
})
}
}
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.
(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.)
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)
Thanks for the info @gaearon! componentWillReceiveProps() seems the way and it's working perfectly. I'm still a novice to the React world, so thanks for pointing the anti-pattern I was doing.
@Blystad that approach is working also, but I should run a profiler to know which is better in terms of performance. We could check how's the garbage collector dealing with the dataSource too. I think both approaches should perform in a very similar way, but for me the componentWillReceiveProps() is more elegant and keeps responsibility separated between the render and the state.
I think both approaches should perform in a very similar way, but for me the componentWillReceiveProps() is more elegant and keeps responsibility separated between the render and the state.
Generally you should strive to avoid state when possible, especially the state synced with props. In this case, I think @Blystad's approach is better, but indeed, you should profile first.
That's interesting @gaearon, do you have any docs or an article about how to deal with state, when to use it and/or best practices? Maybe I'm not using the state correctly but in the Movies app example of Facebook's React Native code, they're using state to feed the <ListView /> data source https://github.com/facebook/react-native/blob/master/Examples/Movies/SearchScreen.js#L66.
I was confused too in the beginning. If you use Redux, you don't really need the state anymore (only for things you don't want to store in the redux stores/state ).
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);
Hi @luqmaan your final code (use dataSource in the mapStateToProps) still works for you?
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?
Instead of setting an attribute against the row, maintain a "selected_row" key: {selected_row: row_id}
Thanks I'll try using index. Also realized that I can use object as data source but then my question is how is the sort order going to be preserved?
@nodkrot The sort order can be preserved in a separate array of identifiers, besides the entity objects dataset. Note that you'll have to keep it in sync with the dataset if the entities get deleted or added.
@sompylasar gotcha, thanks
@Blystad's method worked for me
@Blystad's method worked for me too!!
@Blystad's method worked for me too.
@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 think the root of the problem is somewhere deeper, where dataSource is changed. One of the most recommended practices when dealing with state and redux is immutability. That translates to avoid doing something like:
dataSource.slice() | delete dataSource[customKey] | dataSource.push(...)
but instead do something like:
dataSource = Object.assign({}, dataSource, ...) //if it's a object
dataSource = [...dataSource, newElement] //creating new array
Otherwise, the render method doesn't detect changes
I always wonder about this and come away thinking that this seems soooooo inefficient... a single change to a value in a data-row results in 1) a new array instance 2) garbage collecting old one 3) triggering render method 4) re-running all of that render logic that you may not want to re-run every single time due to a tiny change in a data-cell 5) react doing it's best to figure out what's changed and 6) rendering only the changes or, failing that, re-rendering the entire component.
Is this really the way to go? I'm no react expert so I'm truly interested in determining if this is really the way to go or if there's some other pattern that should be used.
edit: ag-grid seems to have the user actually modify the value directly; I wonder if it's doing the whole immutability thing in the background and thus going through the entire process I already mentioned or if they're doing something more efficient. Check out nameValueSetter method in the example source-code on https://www.ag-grid.com/javascript-grid-value-setters/; they are setting firstName and lastName properties on a data-row directly and returning a boolean true to indicate that the row has changed.
@AnthonyPaulO
I always wonder about this and come away thinking that this seems soooooo inefficient... a single change to a value in a data-row results in 1) a new array instance 2) garbage collecting old one 3) triggering render method 4) re-running all of that render logic that you may not want to re-run every single time due to a tiny change in a data-cell 5) react doing it's best to figure out what's changed and 6) rendering only the changes or, failing that, re-rendering the entire component.
Immutability is good from the large-scale maintainability perspective, but it is a trade-off for performance.
The good part of Redux is the reducers.
The bad part is using a single immutable atom for the whole UI and synchronously updating it when literally anything changes. People like this because it’s conceptually simple, but it’s a huge architectural constraint to impose on a UI.
–@acdlitehttps://twitter.com/acdlite/status/1025408731805184000
Is this really the way to go? I'm no react expert so I'm truly interested in determining if this is really the way to go or if there's some other pattern that should be used.
Newer features of React (Suspense, Async Rendering) are there to address that by distributing state across components and make their rendering dependent on the availability of the data only they require. Google it.
edit: ag-grid seems to have the user actually modify the value directly; I wonder if it's doing the whole immutability thing in the background and thus going through the entire process I already mentioned or if they're doing something more efficient. Check out nameValueSetter method in the example source-code on ag-grid.com/javascript-grid-value-setters; they are setting firstName and lastName properties on a data-row directly and returning a boolean true to indicate that the row has changed.
Angular does dirty-checking behind the scenes: it holds copies of objects and compares them with whatever is mutated in the objects the app has access to (so-called digest), detecting what has changed and re-rendering bound components (via so-called scope watchers).
Most helpful comment
Alas, I don't know enough about what
dataSourceis to help you. The problem is not Redux-specific, and is described in Props in getInitialState is an anti-pattern. You'll get newpropsfrom Redux every time store updates, but you don't use them: you only usestatewhich you initialize once.Does something like this help?
Note that unlike
componentDidUpdate(), asetState()call insidecomponentWillReceiveProps()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.