Skip to content

Instantly share code, notes, and snippets.

@christopherdro
Created October 11, 2015 21:02
Show Gist options
  • Star 40 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save christopherdro/89bc57a19ff02f061954 to your computer and use it in GitHub Desktop.
Save christopherdro/89bc57a19ff02f061954 to your computer and use it in GitHub Desktop.
/**
* @providesModule PatientList
*/
import NavigationBar from 'react-native-navbar';
import NavigationButtons from 'NavigationButtons';
import React, { ListView, Navigator, StyleSheet, Text, TextInput, TouchableHighlight, View } from 'react-native';
import { connect } from 'react-redux/native'
@connect(state => ({
patients: state.patients
}))
export default class PatientList extends React.Component {
constructor(props) {
super(props);
let dataSource = new ListView.DataSource({
sectionHeaderHasChanged: this._sectionHeaderHasChanged,
rowHasChanged: this._rowHasChanged,
});
this.state = {
dataSource: dataSource
}
}
componentDidMount() {
// let listViewScrollView = this.refs.listView.getScrollResponder();
// listViewScrollView.scrollTo(1); // Hack to get ListView to render fully
}
componentWillReceiveProps(nextProps) {
if (nextProps.patients !== this.props.patients) {
let {data, sectionIds} = this._getListViewData(nextProps.patients);
this.setState({
dataSource: this.state.dataSource.cloneWithRowsAndSections(data, sectionIds)
})
}
}
_getListViewData(patients) {
let data = {};
let sectionIds = [];
patients.map((patient) => {
let section = patient.lastName.charAt(0);
if (sectionIds.indexOf(section) === -1) {
sectionIds.push(section);
data[section] = [];
}
data[section].push(patient);
});
return {data, sectionIds};
}
_sectionHeaderHasChanged(oldSection, newSection) {
return oldSection !== newSection;
}
_rowHasChanged(oldRow, newRow) {
return oldRow !== newRow;
}
_onPressButton(rowID, rowData) {
this.props.navigator.push({
id: 'patient-notes',
data: rowData,
sceneConfig: Navigator.SceneConfigs.FloatFromRight,
navigationBar: (
<NavigationBar
title={`${rowData.lastName}, ${rowData.firstName.charAt(0)}.`}
buttonsColor="#48D1CC"
customNext={<NavigationButtons.AddNote patient={rowData} NavigationEmitter={this.props.NavigationEmitter}/>}
style={{flex: 1}}
/>
)
});
}
_renderRow(rowData, sectionID, rowID) {
return (
<TouchableHighlight underlayColor={'#ccc'} onPress={() => this._onPressButton(rowID, rowData)}>
<View>
<View style={styles.row}>
<Text style={[styles.text, styles.boldText]}>{rowData.lastName}, </Text>
<Text style={styles.text}>{rowData.firstName}</Text>
</View>
</View>
</TouchableHighlight>
);
}
_renderSectionHeader(data, sectionId) {
var text;
return (
<View style={styles.sectionHeader}>
<Text style={styles.sectionHeaderText}>{sectionId}</Text>
</View>
);
}
_renderSeparator(sectionID, rowID, adjacentRowHighlighted) {
var style = styles.rowSeparator;
if (adjacentRowHighlighted) {
style = [style, styles.rowSeparatorHide];
}
return (
<View key={"SEP_" + sectionID + "_" + rowID} style={style}/>
);
}
render() {
return (
<ListView
ref="listView"
automaticallyAdjustContentInsets={false}
dataSource={this.state.dataSource}
renderRow={::this._renderRow}
renderSectionHeader={this._renderSectionHeader}
renderSeparator={this._renderSeparator}
/>
);
}
}
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
padding: 15,
},
rowSeparator: {
backgroundColor: 'rgba(0, 0, 0, 0.1)',
height: 1,
marginHorizontal: 10,
},
rowSeparatorHide: {
opacity: 0.0,
},
sectionHeader: {
backgroundColor: '#48D1CC'
},
sectionHeaderText: {
fontFamily: 'AvenirNext-Medium',
fontSize: 16,
color: 'white',
paddingLeft: 10
},
thumb: {
width: 64,
height: 64,
},
text: {
fontFamily: 'AvenirNext-Medium',
fontSize: 16
},
boldText: {
fontFamily: 'AvenirNext-Bold',
},
});
// PatientList.propTypes = {
// // onAddClick: PropTypes.func.isRequired
// };
@davidroman0O
Copy link

Thx a lot!

@stranbury
Copy link

thx

@lukejagodzinski
Copy link

Thanks for that example. I was updating DataSource in the componentWillUpdate event instead of componentWillReceiveProps and app was getting into infinite loop :P

@lodev09
Copy link

lodev09 commented Mar 20, 2017

I think comparing nextProps.patients !== this.props.patients is not reliable. You would need to do a "deep" comparison to actually be able to compare collection changes. Current code will result to always updating your this.state.datasource everytime your component receives props which could be a performance issue.

In your ListView datasource prop, you can actually pass a function that gets the datasource. Any changes to the this.props.patients prop will automatically trigger a render by redux. E.g:

getDatasource() {
  let {data, sectionIds} = this._getListViewData(nextProps.patients);
  return this.state.dataSource.cloneWithRowsAndSections(data, sectionIds);
}
<ListView
  ...
  datasource={this.getDatasource()}
  ...
/>

Correct me if I'm wrong :)

@ofirgeller
Copy link

@lodev09

If you are using redux you should not mutate the state. You concerens might be that:

  1. state changes and we don't update the view
  2. data doesn't change but we think it has so we render the view again and take a pref hit.

Since we know the original array will never change (unless you are abusing redux) we can do a shallow equality check. if the reference is the same the data must be the same.

Now if for some reason a reducer has created a brand new array that has the exact same items (why would you do that?) you will do some more work. but not a lot since _rowHasChanged is going to return false for all items, meaning that when react goes over the list and renders each item it can see the specific item is the same and avoid rendering it again.

If someone actually created a whole new list with whole new items that are copies of the original then yes, you will do extra work. but why would anyone do that?

@tquiroga
Copy link

Thank you for that gist! Very useful

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment