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.
Related
I am rendering a component for every item in a flatList. Each component has a label, and when the component is rendered, I have a useEffect that fetches the updated label name for that specific label.
For some reason, it seems to only be running for the last item in the flatList. The last item is the only item with the updated name, while all other still contain the outdated information.
Assuming there is an updated name for each label, why could my useEffect only be running on the last item?
<FlatList
data={labels}
keyExtractor={keyExtractor}
renderItem={renderItem}
/>
Label.js - I would think this would run for every label component rendered. Could there be a possible issue with what I have here? Or must it be somewhere else in my code?
let name = label.name;
useEffect(() => {
updateLabel()
name = label.name
}, [label]);
return (
<>
{name}
</>
)
I see several possible issues. Some important code is missing, so I'll answer what I can.
You're not using state to hold your label name in the Label component (name = label.name), so React will never know to re-render the component when it changes. It's rare to need to use a let variable in React. To hold properties that the component needs to change, use the useState hook.
However, you shouldn't do that here, because of the next point.
It looks like you are updating the label somewhere else, and also locally (name = label.name). Don't do this, it's too easy for the two to get out of sync and cause bugs. If the name is coming from somewhere else, show it and set it from props.
I'm not sure what updateLabel() does or where it comes from (how does the function know what to update the label to?), but if you need it, it should come from props.
If label.name is a string, you can't render it in a fragment. You must render it in a Text component. <Text>{label.name}</Text>
The object that FlatList passes in to the renderItem callback does not have a property called label, you are looking for item - this is the object from the data prop.
function renderLabel({ item }) { // item, not label
return <Label label={item} onPress={() => onPressLead(item)}/>;
}
const Label = ({ label, updateLabel }) => {
// no local label variable
useEffect(() => {
updateLabel(); // what is this supposed to do?
}, []); // no dependencies, if you only want to update the label once on mount
return <Text>{label.name}</Text>; // if label.name is a string
};
// your FlatList is fine as written
Your use effect probably needs the label as a dependency.
useEffect(() => {
updateLabelName()
}, [label]);
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.
I have an async function like this:
getDeals() {
if(this.props.user) {
this.setState({loading: true});
this.setState({deals: []});
var parameters = {
zip: this.props.user.zip,
sort: 'All',
category: this.props.selectedCategory,
company: this.props.user.company,
page: null,
user: this.props.user,
search: null
}
axios.post(`${constants.api}/grab-deals/`, parameters)
.then((response) => {
this.setState({totalDeals: response.data.length});
this.setState({deals: response.data, loading: false, refreshing: false});
this.forceUpdate();
})
}
}
And a FlatList component Like this:
<FlatList data={this.state.deals} style={{flex: 1, padding: 10}} extraData={this.state} keyExtractor={this.keyExtractor} renderItem={this.renderDeal.bind(this)} />
Here is the keyextractor:
keyExtractor = (item, index) => item.id;
When I call this.getDeals() the first time it works great. However when I call it a second time the axios call get's all of the correct data, but the flat list still keeps old data (it doesn't remove items that aren't in the new call).
How do I get the FlatList to always reflect the returned data?
Call this.getDeals() in componentWillUpdate() and update props?
I believe you confussing what props and state is for. Basically state is used for things that could change during the lifecycle of the component and props are kept immutable. I use them for behavior.
Unless you are changing the parameters for the getDeals function on the second call, see that all of the properties are based on the props, which are not always updated.
RN has a method called componentWillUpdate that is triggered with the new props which you can then be used to update the component itself. If you want to keep using props in your getDeals method, you will need to check if the props have changed (this happens when the parent updates the child with new props) and then trigger again the data fetch.
If this does not help, please post more code.
According to the docs you need to set the state.selected value
By passing extraData={this.state} to FlatList we make sure FlatList
itself will re-render when the state.selected changes. Without setting
this prop, FlatList would not know it needs to re-render any items
because it is also a PureComponent and the prop comparison will not
show any changes.
I'm using react-native-chooser to create my dropdowns and when I select 1 item from dropddown1 I want to update the items from dropdown2. Thanks.
In react, to make your UI change, you need to update your state. From looking at the docs for react-native-chooser it has a callback method called onSelect. Here, the currently selected option is returned to you to use. Based on this selected option you can update the state of second dropdown. The important part here is the parent child relationship. In react, a child is only re-rendered if the parent's state is updated (unless otherwise specified). Some pseudocode:
// Your method callback
onSelect = (option) => {
const newOptions = computeNewOptions(option)
this.setState({options: newOptions})
}
// Your Second dropdown component would take these options in as a prop
render () {
return (
<SecondDropDown options={this.state.options} />
)
}
// You can then access your options through the props
export default class SecondDropDown extends React.Component {
render () {
let myOptions = renderOptions(this.props.options)
return (
<View>
{myOptions}
</View>
)
}
}
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