React Native ListView row not re-rendering after state change - react-native

React Native ListView: Rows are not re-rendering after datasource state has changed.
Here is a simplified version of my code:
render(): {
return <ListView
dataSource={this.state.DS}
renderRow={this.renderRow}/>
}
renderRow(item): {
return <TouchableOpacity onPress={() => this.handlePress(item)}>
{this.renderButton(item.prop1)}
</TouchableOpacity>
}
renderButton(prop1): {
if (prop1 == true) {
return <Text> Active </Text>
} else {
return <Text> Inactive </Text>
}
}
handlePress(item): {
**Change the prop1 of *item* in an array (clone of dataSource), then**
this.setState({
DS: this.state.DS.cloneWithRows(arrayFromAbove)
})
}
According to Facebook's example, ListView is supposed to rerender every time data source is changed. Is it because I'm only changing a property of an item in data source? It seems like renderRow function is not re-rendering, but render() function is from datasource change.
Thank you.

react is smart enough to detect changes in dataSource and if the list should be re-rendered. If you want to update listView, create new objects instead of updating the properties of existing objects.
The code would look something like this:
let newArray = this._rows.slice();
newArray[rowID] = {
...this._rows[rowID],
newPropState: true,
};
this._rows = newArray;
let newDataSource = this.ds.cloneWithRows(newArray);
this.setState({
dataSource: newDataSource
});
You can read more about similar issue on Github

First you need to set the datasource in the getInitialState function. Then, change the datasource by calling this.setState({}) and passing in the new datasource. It looks like you may have been on the right track above, but I have set up a working example of changing the ListView datasource here . I hope this helps
https://rnplay.org/apps/r3bzOQ

Related

React Native doesn't re-render on DOM change

I had an array of components inside a ScrollView component. Somehow react native doesn't re-render when the array is modified.
Here's a demonstration of my problem:
const TestApp = () => {
const [arr, setArr] = useState([]);
function pushArr() {
setArr((arr) => {
arr.push(1);
return arr;
});
console.log('pushArr():', arr);
}
function flushArr() {
setArr([]);
console.log('flushArr():', arr);
}
useEffect(() => {
console.log('useEffect():' , arr);
})
return (
<>
<ScrollView style={{flex:1}}>
{arr.map((elem, i) => <Text key={i}>{elem}</Text>)}
</ScrollView>
<Button title="Push" onPress={pushArr}></Button>
<Button title="Flush" onPress={flushArr}></Button>
</>
)
}
The page remains blank, and no updates happen on button press.
I've logged out arr and these are my findings:
pushArr() and flushArr() works as expected
useEffect() gets triggered only on startup and after flushArr()
Can anyone explain this behavior, and what mistakes have I made?
If I remember correctly, you need to make a copy of the array whenever you want it to “react”. The new memory address will let react know it should update. In other words, you shouldn’t mutate the array.
You can use the spread operator to make a copy and then push an element to the end which you can then pass to useArr. Usually I see people just passing the new object inside your useArr function.
I also don’t see you passing anything to your useArr function.

How to update attribute value by using ref in react native?

In below code there is "opened" attribute and I want to change its value by using ref. Here I am using ref as indexed array.
<Menu renderer={renderers.SlideInMenu} ref={(Menu) => { this.rowRefs[item.id] = Menu; }} opened={false}>
I tried it as
function updateRef(id){
React.findDOMNode(this.refs.id).setAttribute("opened", true);
}
Can anyone please explain how to create an indexed reference and how to use it?
Props should be immutable and for the purpose of dynamically change update them you should consider to set them via state.
Your code should look like:
<Menu renderer={renderers.SlideInMenu} ref={component => this.menuRef = component }} opened={this.state.opened}>
In which case the <Menu .. > is assumed to be rendered in a component which has a state variable opened which you can change using this.setState({opened: true}) . This state change will make your UI rerender hence <Menu .. > will be rendered with opened={true}.
Also if you want to use ref, then you should consider making a state variable inside Menu which should be initialized with opened prop, and you should have a method in the Menu which will change the state.
Your code should look like below:
class Menu extends React.Component {
constructor (props) {
super(props);
this.state = {
menuOpened: props.opened
}
}
changeMenuOpened = (value) => {
this.setState({
menuOpened: value
})
}
.....
}
and then you can just call the changeMenuOpened method using Menu's ref from the parent.
this.menuRef.changeMenuOpened(true);

Using FlatList#onViewableItemsChanged to call a Component function

I'm currently attempting to implement a form of LazyLoading using the FlatList component, which introduces a neat little feature called onViewableItemsChanged which gives you a list of all of the components that are no longer on the screen as well as items that are now on the screen.
This is a custom LazyLoad implementation and as such is more complicated than most LazyLoad open-sourced libraries that are available, which is why I'm working on my own implementation. I'm already looked into react-native-lazy-load and others.
Basically, I need to be able to call a function that's part of the component being rendered in the FlatList, I've tried creating a reference to the item rendered in the FlatList and calling it as such, but it doesn't seem to work.
For example:
<FlatList data={...}
renderItem={(item) => <Example ref={(ref) => this[`swiperRef_${item.key}`] = ref}}
onViewableItemsChanged={this.onViewableItemsChanged}
/>
onViewableItemsChanged = ({viewableItems}) => {
viewableItems.forEach((item) => {
const { isViewable, key } = item;
if(isViewable && !this.cachedKeys.includes(key)) {
const ref = this[`swiperRef_${key}`];
if(!ref) return console.error('Ref not found');
ref.startLoading();
this.cachedKeys.push(key);
}
});
}
Now in the <Example /> component I would have a function called startLoading which should be called when a new visible item is brought onto the screen, however the ref never exists.
I was actually doing everything correctly, but I accidently forgot to deconstruct the parameter returned from the renderItem function, so (item) should have been ({ item })
That's all there was to it.

React Native: Update ListView Row when props changes

I do have a ListView component with a renderRow() function. My custom ListBountiesView component which renders a ListView Row also takes a prop called cr and displays some content in each row depending on the value of cr.
The problem is that when this.props.customerRelationship changes its value, the ListView rows do not get updated.
I am doing it with: this.props.customerRelationship.points += responseJson.points;
I guess that ListView only updates when the data attribute changes, but how can I move props to my renderRow component so that they also update the ListView?
SuperScreen.js
renderRow(bounty) {
const { customerRelationship } = this.props;
return (
<ListBountiesView
key={bounty.id}
bounty={bounty}
cr={customerRelationship}
onPress={this.openDetailsScreen}
/>
);
}
render() {
const { bounties } = this.props;
return (
<ListView
data={bounties}
renderRow={this.renderRow}
loading={loading}
/>
)
The ListView refreshes the data source when the data prop gets a new reference:
componentWillReceiveProps(nextProps) {
if (nextProps.data !== this.props.data) {
this.setState({ dataSource: this.listDataSource.clone(nextProps.data) });
}
if (nextProps.loading !== this.props.loading) {
this.setLoading(nextProps.loading);
}
}
Similarly, React Native's ListView refreshes when its data source changes. This was discussed on their GitHub and in many other threads. The generally accepted workaround is to create a new data array. In your case, do it in componentWillReceiveProps whenever customerRelationship changes.
Move your customerRelationship to bounty object. Each bounty should have this property, then check it's value changed in the rowHasChanged. Other way is to check customerRelationship in componentWillReceiveProps function, if it's value changed clone bounties so all of it's child have new object reference.

Set updated state props on button click using redux

I am trying to create a button which is displaying and hiding a navigation menu on click. I am using Redux to get the current state into the Component, but something is not working with the onPress function.
When pressing the button I want to check the current state of this.state.showNavigation (can be true/false) but I am getting an "undefined is not an object" error immediately after clicking the button.
I think I am running into a lifecycle issue here. I already tried to ship around this via setting the state in componentWillMount like that:
componentWillUpdate(){
this.state = NavigationStore.getState();
}
Anyway that didn't help. Some advise is much appreciated. Thanks!
Heres my code:
class NavigationButton extends React.Component {
constructor(props, context) {
super(props, context);
this.state = NavigationStore.getState();
NavigationStore.subscribe(() => {
this.setState(NavigationStore.getState());
});
// alert(this.state.showNavigation);
}
render() {
return (
<TouchableOpacity
onPress={this.handlePressButton}
style={navigationButtonStyles.button}>
<Image source={buttonImage} />
</TouchableOpacity>
);
}
handlePressButton() {
if(this.state.showNavigation){
NavigationStore.dispatch({
type: 'HIDE_NAVIGATION',
});
}
else{
NavigationStore.dispatch({
type: 'SHOW_NAVIGATION',
});
}
}
};
I was using a pretty strange approach, I did not use the react-redux package for the whole thing and couldn't connect my store. I deep dived into https://github.com/bartonhammond/snowflake and got it solved, the snowflake example was really helpful to understand the basics!