render same dataRow too much every time - react-native

Items re-render multiple times Whenever new Items are added to data prop array. So how can I avoid this useless re-rendering of items.
I am using
react-native 0.59.0
react 16.8.3
This is what I get when I log, Items in it's render method
renderItem 0
renderItem 1
renderItem 2
renderItem 3
// from here render again
renderItem 0
renderItem 1
renderItem 2
renderItem 3
renderItem 4
renderItem 5
renderItem 6
renderItem 7
//gettingData and displaying component
let offset = this.props.fetchedData.length;
//function to fetch data from server
getData() {
fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
limit: 20,
offset,
}),
})
.then(response => (response.json())
.then((responseJson) => {
if (responseJson.status) {
this.props.fetchedDataChange(responseJson.data);
}
})
.catch((error) => {
alert(error);
}));
}
//renderItem function for flatlist
renderItem({ item }) {
return (
<View>
<Text>
{item.name}
</Text>
</View>
);
}
render() {
return (
<View>
<FlatList
data={this.props.fetchedData}
renderItem={{ item } => this.renderItem({ item })}
keyExtractor={(item, index) => index.toString()}
extraData={this.props}
initialNumToRender={20}
maxToRenderPerBatch={20}
onEndReached={() => this.getData()}
onEndReachedThreshold={0.5}
/>
</View>
);
}
const mapStateToDispatchProps = dispatch => ({
fetchedDataChange: value => dispatch(fetchedDataChange(value)
});
const mapStateToProps = state => ({
fetchedData: state.fetchedDataReducer.fetchedData
});
export default connect(mapStateToProps, mapDispatchToProps)(gettingData);
//fetchedData reducer component
const INITIAL_STATE = {
fetchedData: [],
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCHED_DATA_CHANGED:
return {
...state,
fetchedData: [...state.fetchedData, ...action.payload]
};
default:
return state;
}
};

If your data prop is getting updated from state variable everytime state changes re-rendering will occur.

I believe you might be looking for React.PureComponent which implements a shallow comparison to decide if rerender is needed. You can also achieve this by implementing shouldComponentUpdate() on React.Component.
There is already an example in FlatList's docs that demonstrate how you can use PureComponent to render your list, just scroll down abit and look for the "more complex example" :)
If you prefer to use a Functional Component instead of a Class Component, checkout React.memo, which is similar to React.PureComponent but for Functional Components. Hope it helps.

Related

Re render flat list when data change cause infinite loop React Native

I have two screens. Approve List and Approve Detail. When data approved in Approve Detail, page navigate to Approve List. Then approved data should disapear from FLatList. How to remove FlatList item when data approved? or how to re render FlatList when data change? Here is my code:
Approve List:
const Approve = ({ navigation }) => {
const [rekomendasi, setRekomendasi] = useState({})
// other code
const getRekomendasi = async (token, bagian) => {
try {
const response = await sippApi.get(`/penjaminan?bagian=${bagian}`, {
headers: {
Auth: token
}
});
setRekomendasi(response.data.data)
console.log(rekomendasi)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
getToken();
getUserData()
getRekomendasi(token, userData.bagian);
}, [setToken, setUserData, rekomendasi]); // if I pass rekomendasi here, make infinite loop on api request
return (
<FlatList
onRefresh={() => onRefresh()}
refreshing={isFetching}
removeClippedSubviews
style={{ marginTop: 2 }}
data={rekomendasi}
keyExtractor={rekom => rekom.penjaminan.nomor_rekomendasi}
renderItem={({ item }) => {
return (
<TouchableOpacity onPress={() => navigation.navigate("ApproveDetail", { id: item.penjaminan.nomor_rekomendasi, bagian: userData.bagian })}>
<ApproveList
plafond={item.value}
kantor={item.nama_kantor}
nomor_rekomendasi={item.nomor_rekomendasi}
produk={item.skim}
/>
</TouchableOpacity>
)
}}
showsHorizontalScrollIndicator={false}
/>
)
}
If I pass value on second argument on UseEffect, it cause infinite loop on API request. If not, my FlatList cant re render when data change. What should I do?
Thanks for help
You have to remove the rekomendasi dependency in the useEffect to avoid infinite loop, it's only for init data :)
What is the purpose of onRefresh function in the FlatList ? Instead you could put the getRekomendasi function to trigger a new call and your data will be updated
try to separate the functions to two useEffects
useEffect(() => {
//<-- write your getToken() and getUserDate() here
getToken();
getUserData()
}, []);
useEffect(() => {
const getRekomendasi = async (token, bagian) => {
try {
const response = await sippApi.get(`/penjaminan?bagian=${bagian}`, {
headers: {
Auth: token
}
});
setRekomendasi(response.data.data)
console.log(rekomendasi)
} catch (error) {
console.log(error)
}
}
getRekomendasi(token, userData.bagian);
},[token,userData.bagian]);
Problem solved by using useFocusEffect
useFocusEffect(
React.useCallback(() => {
getRekomendasi(token, userData.bagian)
}, [token, userData.bagian])
);

FlatList items re-rendering even with React.memo

I am trying to render a list of items in React Native with the FlatList component but every time I fetch new data it re-renders the who list of items even with React.memo.
Here is what my code looks like:
const data = [
{ _id: 1, text: 'Hello World' },
{ _id: 2, text: 'Hello' },
{ ... }
]
const renderItem = ({ item }) => (<Component item={item} />)
const loadMore = () => {
//Fetching data from db and adding to data array
}
<FlatList
data={data}
keyExtractor={item => item._id}
renderItem={renderItem}
onEndReached={loadMore}
removeClippedSubviews={true}
/>
Component.js
const Component = ({ item }) => {
console.log('I am rendering')
return (
<Text>{item.text}</Text>
)
}
const equal = (prev, next) => {
return prev.item.text === next.item.text
}
export default React.memo(Component, equal)
Every time the onEndReached function gets triggered and calls the loadMore function, all FlatList items get re-rendered, it console.log 'I am rendering' every single time and causes the error virtualizedlist you have a large list that is slow to update
Thanks to anyone who can help me!
I don't know why but I fixed it with an if statement in the equal function
//Changing this
const equal = (prev, next) => {
return prev.item.text === next.item.text
}
//To this
const equal = (prev, next) => {
if(prev.item.text !== next.item.text) {
return false;
}
return true
}
Hope this could help someone else.

React Native: How to change inputfield to selected item

So what I'm trying to do is fetching data from an API (works well), that has this autocomplete function.
Link to example: https://autocomplete.aws.dk/
Link to the guide: https://autocomplete.aws.dk/guide2.html
What is hard for me, is that the guide is HTML, and this is suppose to work in React Native.
So far I made an input field, that can detect when writing minimum two letters will show a list of addresses.
What I want is when the address is clicked, it takes that value and places it in the input field.
Heres my code:
The API fetch:
import React from "react";
import url from "./url";
export default class DawaFetch extends React.Component {
static defaultProps = {
options: {},
minCharacters: 2,
};
state = {
value: "",
suggestions: [],
};
handleChange = ({ target: { value } }) => {
this.setState({ value });
if (this.props.minCharacters <= value.length) this._fetch(value);
};
_fetch = (value) => {
fetch(
url("https://dawa.aws.dk/adresser/autocomplete", {
q: value,
["per_side"]: 100,
...this.props.options,
}),
{
method: "GET",
headers: {
"Accept-Encoding": "gzip, deflate",
},
}
)
.then((response) => response.json())
.then((json) => this.setState({ suggestions: json }))
.catch((err) => console.error("parsing failed", err));
};
render = () =>
this.props.children({ ...this.state, handleChange: this.handleChange });
}
And here is my view:
<DawaFetch>
{({ value, suggestions, handleChange }) => {
console.log(suggestions);
return (
<View>
<CustomInputs
type="text"
value={value}
onChange={handleChange}
/>
{suggestions.map((suggestion) => (
<TouchableOpacity>
<NormalText key={suggestion.adresse.id}>{suggestion.tekst}</NormalText>
</TouchableOpacity>
))}
</View>
);
}}
</DawaFetch>
Tried different solutions by making it a FlatList with renderItem, and making an onPress function, but I could never make it work.
Hopefully someone can guide me in the right direction, I might been overthinking this.
React-Native TextInput onChange value is not available inside the target as it's available in HTML, Remove target from handleChange function like below, also it's not onChange it's onChangeText in TextInput.
handleChange = (value) => {
this.setState({ value });
if (this.props.minCharacters <= value.length) this._fetch(value);
};
You can achieve your desired functionality in a very simple manner.
Add this to your DawaFetch class.
OnItemSelection=(address)=>{
this.setState({value: address})
}
Add this to your render Function
render = () =>
this.props.children({ ...this.state, handleChange: this.handleChange, OnItemSelection: this.OnItemSelection });
}
Then make these changes in your DawaFetch component
<DawaFetch>
{({ value, suggestions, handleChange, OnItemSelection }) => {
console.log(suggestions);
return (
<View>
<CustomInputs
type="text"
value={value}
onChangeText={handleChange}
/>
{suggestions.map((suggestion) => (
<TouchableOpacity onPress={()=> OnItemSelection(suggestion.adresse)}>
<NormalText key={suggestion.adresse.id}>{suggestion.tekst}</NormalText>
</TouchableOpacity>
))}
</View>
);
}}
</DawaFetch>
Edit:Here is Snack having solution
https://snack.expo.io/#waheed25/bad-raisins

How to pass a list with objects from Server to FlatList?

I'm trying to create a FlatList that contains an array with objects that has an array from a server and I can't get it to work.
my error message:
TypeError: Cannot read proparty 'Data' of undefined
I can get it to work with my normal list that's not fetched from a Server. Heres the structure from my working list
[{"DATA":[{"filter_id":"44","filter_name":"filter 1"}, {"filter_id":"45","filter_name":"filter 2"},{"filter_id":"46","filter_name":"filter 3"},{"filter_id":"47","filter_name":"filter 4"},{"filter_id":"48","filter_name":"filter 5"}],"MESSAGE":"DATA FOUND","STATUS":200}]
My server list have the same structure but different values of filter_name and filter_id
here's my code:
constructor(props){
super(props);
this.state = {
data: [],
oldData: [{"DATA":[{"filter_id":"44","filter_name":"filter 1"},{"filter_id":"45","filter_name":"filter 2"},{"filter_id":"46","filter_name":"filter 3"},{"filter_id":"47","filter_name":"filter 4"},{"filter_id":"48","filter_name":"filter 5"}],"MESSAGE":"DATA FOUND","STATUS":200}],
page:1,
status: null,
isLoading: false,
}
}
getData = async () => {
const url = 'api/getFilter.php?page='+this.state.page+'&row_per_page=5';
fetch(url).then((response) => response.json())
.then((responseJson) => {
this.setState({
data:this.state.data.concat(responseJson),
isLoading:false
});
})
}
componentDidMount() {
this.getData();
}
renderRow = ({item}) => {
console.log('item', item);
return (
<View style={styles.item}>
<Text>{item.filter_name}</Text>
</View>
)
}
render() {
console.log('state', this.state.data[0]);
console.log('oldstate', this.state.oldData[0]) // this
return (
<View style={styles.container}>
<FlatList
data={this.state.data[0].DATA}
renderItem={this.renderRow}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
Expo: https://snack.expo.io/#thesvarta/tenacious-sandwich
The issue is that on the initial rendering of your component this.state.data is empty, because we have to wait until getData returns any data. That's why you cannot access this.state.data[0].DATA at the beginning.
The solution is to update your getData function a little bit.
getData = async () => {
const url = 'http://ollenorstrom.se/ollenorstrom.se/avoka/api/getFilter.php?page='+this.state.page+'&row_per_page=5';
fetch(url).then((response) => response.json())
.then((responseJson) => {
// here we save the data, we want to access later.
console.log('responseJson', responseJson[0].DATA);
this.setState({
data:this.state.data.concat(responseJson[0].DATA),
isLoading:false
});
})
}
Now your data is directly stored in this.state.data. We now can simplify your render() function:
<FlatList
data={this.state.data} // simplified, passing an empty array at the beginning is ok
renderItem={this.renderRow}
keyExtractor={(item, index) => index.toString()}
/>
Working example:
https://snack.expo.io/HJ--GFlnN

React Native implement JSON data on ListView

I'm having a problem with implementing API data in ListView. I fetched JSON using Axios.
export function fetchRateService() {
return function(dispatch) {
axios.get(RATE_URL)
.then(response => {
dispatch({
type: FETCH_RATE_SERVICE,
payload: response.data
});
})
.catch((error) => {
console.log(error);
})
}
}
Reducer. I added rates data into array
import {
FETCH_RATE_SERVICE
} from '../actions/types';
const INITIAL_STATE = {
base: '',
date: '',
rates: []
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_RATE_SERVICE:
return {
...state,
base: action.payload.base,
date: action.payload.date,
rates: [ ...state.rates, action.payload.rates ]
};
default:
return state;
}
};
This is the component
class ConturyList extends Component {
componentWillMount() {
this.props.fetchRateService();
this.createDataSource(this.props);
}
createDataSource({rates}) {
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.dataSource = ds.cloneWithRows(rates);
}
renderRow(rate) {
return <ListItem rate={rate} />
};
render() {
console.log(this.props);
const { CardSectionStyle, textStyle, containerStyle } = styles;
const { visible, closeModal } = this.props;
return (
<Modal
visible={visible}
transparent={false}
animationType="slide"
onRequestClose={() => {this.props.closeModal()}}
>
<ListView
enableEmptySections
dataSource={this.dataSource}
renderRow={this.renderRow}
/>
</Modal>
);
}
}
const mapStateToProps = state => {
return {
rates: state.rateService.rates,
date: state.rateService.date,
base: state.rateService.base
};
}
export default connect(mapStateToProps, { fetchRateService } )(ConturyList);
The problem is I can see the props data using console.log(this.props);
enter image description here
I'm spending more than 3 days to figure out why this is not working. I tried using map() adding on
renderRow(rate) {
return rate.map((data) => {
return <ListItem rate={data} />
};
but it did not work. All the conutry code is in one object, Do I need to split the data by commas?.
Appreciate you helps. Thank you
UPDATE
So I'm trying to implement FlatList instead using ListView. The problem is on the JSON data. enter image description here. I want to implement key which are CountryCurrencyCode(AUD, JPN, etc..) to FlatList. Since rates is an object within an object, I added rates object into an array(reducer). But this.props.rates[0] can't be implemented on data property of FlatList. What kind of method can I try? I can't think of anything. I could print out key using map() when rates is object and then I can't implement it on the FlatList.
I would recommend switching over to the new FlatList component over ListView. FlatList just accepts an array of data to hydrate.
Initiate this.state.datasource as an empty array
constructor(props) {
super(props);
this.state = {
dataSource: [],
}
}
Fetch your data and hydrate this.state.dataSource from your Redux reducer/action
ComponentDidMount(){
this.props.fetchRateService();
var myData = this.props.rates[0];
this.setState({
dataSource:myData
)}
}
Now that your this.state.dataSource is set, we can populate FlatList
<FlatList
data={this.state.dataSource}
renderItem={({item})=>this.renderRow(item)}
/>
Flat List will throw a warning about a key extractor
Add this line below to the FlatList component. You will need to change 'item.key' to fit your own unique child. You can just keep it out for now for development.
keyExtractor={item => item.key}
You should see your data now! Keep in mind, you don't have to set the this.state.dataSource. Its just how I do it. You can plug 'this.props.rates' array directly into FlatList instead. Check out the FlatList docs for all the different things you can do with it. Hope this helps!