Can rxjs control the maximum frequency of a function call? - reactjs

I am making a react-native app. Since the onEndReached props in FlatList is problematic, onEndReached can be triggered more than once when the end is reached.
I have heard of rxjs that can make a button's onPress only be triggered once with some condition even user is clicking on it multiple times.
Below is the Flatlist:
<FlatList
data={paginatedList}
ListHeaderComponent={() => this.renderHeader()}
renderItem={({item, index}) => this.renderItem(item, index)}
onEndReachedThreshold={0}
onEndReached={(distanceFromEnd) => {
console.log(distanceFromEnd);
this.setState({normalListLength: normalListLength + 10})
}}
/>
I want the this.setState function to be limited to once per seconds (1000ms) . Is that I should use rxjs to do this?

So one possible solution could be to have a Subject which you can next() the new value (distanceFromEnd) into. And then you can apply any combination of operators (including debounceTime) to enforce the frequency limiting.
Keep in mind my React syntax might not be spot on
<FlatList
data={paginatedList}
ListHeaderComponent={() => this.renderHeader()}
renderItem={({item, index}) => this.renderItem(item, index)}
onEndReachedThreshold={0}
onEndReached={(distanceFromEnd) => {
console.log(distanceFromEnd);
myOnEndReachedSubject.next(distanceFromEnd);
this.setState({normalListLength: normalListLength + 10})
}}
/>
// elsewhere define subject
myOnEndReachedSubject = new Subject<number>();
// ....elsewhere in a lifecycle function
componentDidMount() {
myOnEndReachedSubject
.debounceTime(1000) // debounce for a second
.distinctUntilChanged()
.subscribe((distance) => {
// Do something with distance
// setState etc
});
}

Related

flatlist does not load extra data from onEndReached in React Native

I have a FlatList in which currently I am showing 6 items currently and when user scroll to bottom I will add more data using onEndReached function but the issue is this that if I do not add any condition like loading true then onEndReached runs multiple times and if I add loading===true then it will not going to run for a single time. I want to load more data when user reaches to screen end.
<FlatList
data={data}
renderItem={({item, index}) => (
)}
numColumns={2}
keyExtractor={(item, index) => index.toString()}
style={{marginTop: height * 0.01}}
showsVerticalScrollIndicator={false}
onEndReached={(info) => {
isLoading&&
setData([...data, ...topicsPagination]);
console.log('onEndReached', info);
}}
onEndReachedThreshold={0.3}
ListFooterComponent={renderFooter}
bounces={true}
/>
// load more function
loadMore = () => {
setData([...data, ...topicsPagination]);
stopFetchMore = true;
setIsLoading(false);
};
Please help me to debug this issue so that I will be able to load data.

React Native Expo VirtualizedList how to use scrollToEnd method?

I am trying to use a scrollToEnd() method on VirtualizedList. But interestingly I cannot find any example on the internet explaining how to use methods with VirtualizedList.
I tried to use it like below, but obviously it didn't work
<VirtualizedList
data={serverData}
initialNumToRender={4}
renderItem={renderItem}
keyExtractor={item => item.id.toString()}
getItemCount={getItemCount}
getItem={getItem}
ListFooterComponent={renderFooter}
scrollToEnd={{ animated: true }}
/>
The scrollToEnd function is inherited from ScrollView by VirtualizedList. It is a function, not a prop.
You need to create a reference to the list component and call the function using this reference. This can be done as follows.
const listRef = useRef(null);
<VirtualizedList
ref= {listRef}
onContentSizeChange= {()=> listRef.current.scrollToEnd()}
/>
The above code scrolls to the end of the list when the content size of its data changes. However, you can call the scrollToEnd function whenever you want to.
VirtualizedListexpose scrollToEnd as method not props. You have to use React Ref to access it.
const listRef = React.useRef(null)
// Scroll to list end - Call this function after add new items to list for example
const scrollToListEnd = ()=> listRef.current?.scrollToEnd({ animated: true })
<VirtualizedList
data={serverData}
initialNumToRender={4}
renderItem={renderItem}
keyExtractor={(item) => item.id.toString()}
getItemCount={getItemCount}
getItem={getItem}
ListFooterComponent={renderFooter}
ref={listRef}
/>;

Crash when doing scrollToLocation on SectionList

We have an edge case in our app. After the UI is rendered and the user tries to scroll to a section it throws scrolltoindex should be used in conjunction with getitemlayout or on scrolltoindex failed. Now this happens only when he does this immediately after UI render.
_scrollToSection = index => {
setTimeout(() => {
this.list.scrollToLocation({
animated: true,
itemIndex: -1,
sectionIndex: index,
viewPosition: 0
});
}, 150);
};
Section list render:
<SectionList
sections={this.props.sections}
extraData={this.props.subscriber}
ref={ref => {
if (ref) {
this.list = ref;
}
}}
automaticallyAdjustContentInsets={true}
contentInsetAdjustmentBehavior={'automatic'}
windowSize={100}
ListHeaderComponent={this.props.header || null}
ItemSeparatorComponent={() => (
<Separator
style={[mediumStyle.separatorEnd, { backgroundColor: IOS_GREY_02_03 }]}
/>
)}
renderSectionFooter={() => <View style={{ height: 17 }} />}
keyExtractor={(item, index) => index}
removeClippedSubviews={false}
stickySectionHeadersEnabled={true}
renderSectionHeader={({ section }) => (
<SectionTitle title={section.title} theme={this.props.theme} />
)}
renderItem={this._renderItem}
onEndReachedThreshold={0}
onEndReached={() => HapticFeedback.trigger()}
scrollEventThrottle={16}
/>
I tried to google the cause but was unsuccessful finding only outdated and closed issues without a solution. Did this happen to anybody else? How did you fixed it?
UPDATE:
We have come up on a solution of constant item sizes which also takes in count the accessibility scale factor. So we had an item and header size we could use in getItemLayout. All worked as should, but the SectionList is glitchy. When we scrolled to lower sections, the list was jumpy by itself without any interaction.
So far the best solution we had was to build the section list ourselves in native code and use that instead of the RN list.
You're getting this error because scrollToIndex has failed and you have not implemented getItemLayout or onScrollToIndexFailed
getItemLayout in a section list is not exactly fun to setup however this medium post goes over how to do it https://medium.com/#jsoendermann/sectionlist-and-getitemlayout-2293b0b916fb
They suggest react-native-section-list-get-item-layout to calculate the sizes of the layout https://github.com/jsoendermann/rn-section-list-get-item-layout
onScrollToIndexFailed is easier to setup you can add the prop onScrollToIndexFailed={(info) => { /* handle error here /*/ }} You can catch the error and then decide on how you will handle it here.
I would also add a check to make sure that your reference to this.list exists before calling the scrollToLocation function. Something like this.
_scrollToSection = index => {
setTimeout(() => {
if (this.list) {
this.list.scrollToLocation({
animated: true,
itemIndex: -1,
sectionIndex: index,
viewPosition: 0
});
}
}, 150);
};

ReactJS - Dynamic State

How do I dynamically create the state so I can assign 'open'/'close' based on front-end requirements?
E.g.
I have a table with rows in which I have a Fab component. That component has to set a unique ID to open/close to handle the animation. I do not know how many of these rows I will have so I can't pre-populate the state with active1, active2 etc.
Code Example (In this example I am passing 'active1', 'active2' with the object manually, but realistically that isn't a real approach I feel.
{Object.keys(characters).map((k, i) => {
<Fab
active= // how do I specify the state of 'this' instance?
direction="left"
containerStyle={{}}
style={{
backgroundColor: "#5067FF"
}}
position="topRight"
onPress={() => this.setState({ //how so I specify 'this' instances state? })}>
...redacted
I've tried to add a sample similar to my code. Let's say I am passing in 5 Users and each User should have this Fab. At this point my state is blank so I have tried implementing a for each loop into componentdidmount to populate the state, this worked, but I had two issues based on how I managed state.
A) All fabs were open, always.
B) No fabs would ever open.
This is because of my onPress not updating the correct state.
I feel like I have to be way over-complicating this issue and I am tearing up my code to get it to work, so I figured I'd come here to ask.
Let me know what other code I can share.
A way of achieving this is to keep an activeArray in state,
which you can toggle activeness of an element buy pushing or removing the element (or just its id or some property which is unique) from activeArray.
So there should be a function on onPress like:
handlePress = (k) => {
if(this.state.activeArray.indexOf(k) > -1){
// remove element
// for example:
this.setState(state => ({activeArray: state.activeArray.filter(activeElement => activeElement !== k)}))
} else {
// push element
// for example:
this.setState(state => ({activeArray: [...state.activeArray, k]}))
}
}
Then for Fab component:
<Fab
active={this.state.activeArray.indexOf(k) > -1}
direction="left"
containerStyle={{}}
style={{
backgroundColor: "#5067FF"
}}
position="topRight"
onPress={() => this.handlePress(k)}>
This line:
active={this.state.activeArray.indexOf(k) > -1}
means if k is inside activeArray then active is true.
You can set the dynamic state using JSON bracket ([]) notation.
{
Object.keys(characters).map((k, i) => {
let dynamicStateName = "active"+i //it will set the dynamicStateName as active with index. ie, active1, active2 and so on
return(
{ this.state[dynamicStateName] !== false || this.state[dynamicStateName] !== undefined ?
(<Fab
active= {dynamicStateName}// specify the state of 'this' instance here as dynamicStateName
direction="left"
containerStyle={{}}
style={{
backgroundColor: "#5067FF"
}}
position="topRight"
onPress={() => {(this.state[dynamicStateName] != undefined) ? (this.state[dynamicStateName] ? this.setState({ [dynamicStateName]: false }) : this.setState({ [dynamicStateName]: true })) : this.setState({ [dynamicStateName]: true })} } /> // it will set the state as true if undefined. It will act as toggleable
):
( <button onPress={() => this.setState({ [dynamicStateName]: true })}>OpenFabButton<button>
// initially the dynamicState will be either undefined or false. At that time the button will be show. On clicking the button it will enable the fab component
)
)
})
}
This will fix yours

VirtualizedList: You have a large list that is slow to update

I use FlatList with large number of items. I get following alert from Expo XDE.
VirtualizedList: You have a large list that is slow to update - make
sure your renderItem function renders components that follow React
performance best practices like PureComponent, shouldComponentUpdate,
etc. {"dt":13861,"prevDt":1498372326027,"contentLength":6624}
I used some optimization approaches to my FlatList for example PureComponent, but I still get this alert. Before I will describe my optimizations, could you tell me if this alert appears always even though FlatList is optimized? Or maybe it indicated actual issues with performance? I ask because performance of my FlatList is good.
I was previously seeing this error. After optimizing my code, I no longer see it. I figured out the problem by adding console.log() statement to the render() function of the Component that creates the FlatList, and the function that renders each item in the List. I noticed that my code was previously re-rendering the entire FlatList and all its items whenever there's a state change to any component on that page (even a component that's not related to the FlatList). I fixed this by converting various components to PureComponents. Here's what my FlatList declaration looks like:
<FlatList
ref={(ref) => { this.flatListRef = ref; }}
data={allPosts}
initialNumToRender={7}
renderItem={({ item }) =>
<Post postJson={item} isGroupAdmin={isGroupAdmin} user={user} />
}
/>
Notice that I'm returning <Post /> which is a pure component:
import React, { PureComponent } from 'react';
class Post extends PureComponent {
render() { ... }
}
This ensures that the FlatList re-renders a only if the post changes. When I was previously passing a normal function to renderItem i.e., a function that does something like this:
return (
<View>
...
</View>
);
I noticed that the FlatList was re-rendering all items whenever any item changed. Now, by using a PureComponent, the FlatList only renders the new item added to the list (if the list is already being displayed).
It still takes relative long to render the entire list the first time. However, initialNumToRender ensures that the screen is filled up pretty much instantaneously (while the remain items get rendered in the background). And more importantly, after that initial rendering, the FlatList only ever has to render one item at a time (the item that changes).
I found this post very helpful).
I've just realized this is also explained here
I noticed that the answer to this question dosen't proffer solution for those using functional component and hooks. I encountered this problem and i was able to get rid of it by using the hook "useMemo()"
<FlatList
keyExtractor={keyExtractor}
data={productsState.products}
renderItem={renderItem}
/>
const renderItem = ({ item }) => (
<ListItem
title={item.ProductName}
subtitle={(item.ProductQuantity) + " " + (item.QuantityType !==
null ? item.QuantityType : " ") }
bottomDivider
topDivider
chevron
checkmark={checkMark}
onLongPress={() => setCheckMark(!checkMark)}
rightSubtitle={(item.Currency !== null ? item.Currency: " " ) +
" " + (item.productCost !== null ? item.productCost: " " )}
rightSubtitleStyle={{ marginTop: -20 }}
badge={{ value: item.sellingPrice, textStyle: { color: 'orange' }, containerStyle: { marginTop: -20 } }}
/>
)
The renderItem function is an expensive computation, because it a long list to render. Instead I memoize it as follows
const memoizedValue = useMemo(() => renderItem, [productsState.product]);
<FlatList
keyExtractor={keyExtractor}
data={productsState.products}
renderItem={memoizedValue}
/>
const renderItem = ({ item }) => (
<ListItem
title={item.ProductName}
subtitle={(item.ProductQuantity) + " " + (item.QuantityType !==
null ? item.QuantityType : " ") }
bottomDivider
topDivider
chevron
checkmark={checkMark}
onLongPress={() => setCheckMark(!checkMark)}
rightSubtitle={(item.Currency !== null ? item.Currency: " " ) +
" " + (item.productCost !== null ? item.productCost: " " )}
rightSubtitleStyle={{ marginTop: -20 }}
badge={{ value: item.sellingPrice, textStyle: { color: 'orange' }, containerStyle: { marginTop: -20 } }}
/>
)
Don't forget to import useMemo from react, inorder to make this work.
Good Luck!
If you are using a functional component, wrapping the component in memo is a good way to prevent unnecessary renders without going through the hassle of converting a functional component to a pure class component. This post explains it more
follow this example:
In the parent component:
import React from 'react';
import {FlatList} from 'react-native';
import PostCard from './PostCard';
export const NewsFeeds = props => {
return (
<FlatList
data={data}
initialNumToRender={4}
refreshing={loading}
renderItem={_renderitem}
/>
);
};
const _renderitem = ({item}) => <PostCard item={item} />;
In the child component
import React, {memo} from 'react';
import {View} from 'react-native';
const PostCard = (props) => {
return (
<View>
</View>
);
};
export default memo(PostCard);
If you are using a class component, make sure your component is a pure component by extending React. PureComponent in your class definition
class NewsFeeds extends React.PureComponent {
render() {
return (
<FlatList
data={data}
initialNumToRender={4}
refreshing={loading}
renderItem={_renderitem}
/>
)
}
}
Adding this prop :
initialNumToRender={n}
worked for me (n being a considerably short amount, for example 5).
I figured it out, why this bug is happened. The main problem is, when your onEndReached event is happened, im sure you are loading something from server, which means, you need to wait until your loading is finished from server, so after that you can call onEndReached event.
But in your case there is multilple calling of onEndReached event. So when it happens, your application was trying to load datas from server again and again.
Ok, how to solve this problem: you need to create new state, for example
this is realization of infinite scrolling by pagination.
const [loader, setLoader] = useState<boolean>(false);
const onEndReached = (page) => {
if (next && !loader) {
setPage(page + 1)
}
}
const loadData = async () => {
setLoader(true);
const resp = await getData();
setLoader(false);
}
<FlatList ...someprops onEndReached={onEndReached} />
On top of all the answers given, you can also try setting removeClippedSubviews to true.
<FlatList
removeClippedSubviews
// ...other props
/>
By enabling removeClippedSubviews the memory is freed up when an item disappears from the view. When you have a long and complex list (i.e. a list of cards) the DOM of each card can get pretty large so it's best to free up the memory when it's not visible.
In addition if you combine with useCallback() rather than useMemo() you free up a bit more memory when your "data" changes
const renderItem = useCallback(originalRenderItem, [data])
the useMemo() approach will memoize based on the value, but it should really free itself up when the data changes. By doing useCallback() you're getting the benefit of using the "function as a parameter" so you don't need to
const renderItem = useCallback(({item, index}
=> originalRenderItem({item, index}), [data])
Thus making it look like a wrapped function without as much reading for the next person.
Doing this two:
prevents the calling the potentially expensive render() function of recently updated components.
reduces memory used by invisible components
frees up the memoized data if data changes sooner.
Also make sure, you don't encapsulate FlatList with ScrollList.
For me it accidentally appears, because I used native-base, and didn't noticed, that their Component <Content> replace ScrollList.
For more Information see here: https://stackoverflow.com/a/54512633/1256697
add memo to your renderItem component when export it
import React,{memo} from "react";
.
.
.
your code
.
.
.
export default memo(your component name);

Resources