React native List View onEndReached calling multiple times - javascript

I am facing some trouble using the List View onEndReached component in react native.
Render code:
#autobind
_fetchMoreHistory(){
console.log("Fetch more history called");
}
<View style={[styles.Ctr]}>
<ListView dataSource={this.state.txHistorySource}
renderRow={ this._renderRow }
onEndReached ={ this._fetchMoreHistory }
onEndReachedThreshold = {10}/>
</View>
The moment I open the screen _fetchMoreHistory is called twice and works normally after that onEndReached reached. Can someone help debug this ?

I faced the same issue and searched a lot but didn't find any answers, so I used a condition to check if the first request got the data I fire onendreashed again else I don't
Example
// onEndReached
If(condition) {
Make the call
}

So my solution is simple. Don't use onEndReached at all. Use onScroll and detect the end of the scroll.
isCloseToBottom = ({ layoutMeasurement, contentOffset, contentSize }) => {
const paddingToBottom = 20; // how far from the bottom
return layoutMeasurement.height + contentOffset.y >=
contentSize.height - paddingToBottom;
};
and the FlatList component
<FlatList
data={data}
onScroll={({ nativeEvent }) => {
if (this.isCloseToBottom(nativeEvent)) {
// Dont forget to debounce or throttle this function.
this.handleOnEndReached();
}
}}
/>

I had the same issue. But I figured out that I had the ScrollView that wraps my FlatList.
When I removed it all started working properly.
It's a pity that NOTHING WAS SAID ABOUT THAT IN THE OFFICIAL DOCS

You can try my solution
You should configure limit > 10. Example limit = 15
Add onMomentumScrollBegin prop to your ListView declaration.
<ListView
data={this.props.data}
onEndReached={...}
onEndReachedThreshold={0.5}
...
onMomentumScrollBegin={() => { this.onEndReachedCalledDuringMomentum = false; }}
/>
Modify your onEndReached callback to trigger data fetching only once per momentum.
onEndReached =()=> {
if(!this.onEndReachedCalledDuringMomentum) {
this.props.fetchData();
this.onEndReachedCalledDuringMomentum = true;
}
};

I've solved this problem by creating a state variable that tells me is the service is loading or not.
class Component1 extends Component {
public constructor(props) {
super(props);
this.state = {
isLoading: false,
listOfItems: []
};
}
public componentDidMount() {
this.loadData();
}
public render(){
return (
<FlatList
data={this.state.listOfItems}
renderItem={this.renderItem}
onEndReachedThreshold={0.1}
onEndReached={this.loadData}
/>
);
}
private loadData = () => {
if (this.state.isLoading === true) {
/// Avoid make another request is there's happening a request right now
return;
}
this.setState({isLoading: true});
this.fetchData()
.then(() => {
/// Handle success response
this.setState({isLoading: false});
})
.catch(() => {
this.setState({isLoading: false});
});
}
}

You can write the code for fetching data in onMomentumScrollEnd rather than onEndReached, like this:
onMomentumScrollEnd={() => this.props.onEndReached()}
It might not be written as the available prop in the react native documentation, but if you will see the source code for FlatList, it uses Virtualized List which in return has the mentioned prop available.

Related

Component is loading twice [duplicate]

I don't know why my React component is rendering twice. So I am pulling a phone number from params and saving it to state so I can search through Firestore. Everything seems to be working fine except it renders twice... The first one renders the phone number and zero points. The second time it renders all the data is displayed correctly. Can someone guide me to the solution.
class Update extends Component {
constructor(props) {
super(props);
const { match } = this.props;
this.state = {
phoneNumber: match.params.phoneNumber,
points: 0,
error: ''
}
}
getPoints = () => {
firebase.auth().onAuthStateChanged((user) => {
if(user) {
const docRef = database.collection('users').doc(user.uid).collection('customers').doc(this.state.phoneNumber);
docRef.get().then((doc) => {
if (doc.exists) {
const points = doc.data().points;
this.setState(() => ({ points }));
console.log(points);
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
const error = 'This phone number is not registered yet...'
this.setState(() => ({ error }));
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
} else {
history.push('/')
}
});
}
componentDidMount() {
if(this.state.phoneNumber) {
this.getPoints();
} else {
return null;
}
}
render() {
return (
<div>
<div>
<p>{this.state.phoneNumber} has {this.state.points} points...</p>
<p>Would you like to redeem or add points?</p>
</div>
<div>
<button>Redeem Points</button>
<button>Add Points</button>
</div>
</div>
);
}
}
export default Update;
You are running your app in strict mode. Go to index.js and comment strict mode tag. You will find a single render.
This happens is an intentional feature of the React.StrictMode. It only happens in development mode and should help to find accidental side effects in the render phase.
From the docs:
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:...
^ In this case the render function.
Official documentation of what might cause re-rendering when using React.StrictMode:
https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects
This is because of React Strict Mode code.
Remove -> React.StrictMode, from ReactDOM.render code.
Will render 2 times on every re-render:
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Will render 1 time:
ReactDOM.render(
<>
<App />
</>,
document.getElementById('root')
);
React is rendering the component before getPoints finishing the asynchronous operation.
So the first render shows the initial state for points which is 0, then componentDidMount is called and triggers the async operation.
When the async operation is done and the state been updated, another render is triggered with the new data.
If you want, you can show a loader or an indicator that the data is being fetched and is not ready yet to display with conditional rendering.
Just add another Boolean key like isFetching, set it to true when you call the server and set it to false when the data is received.
Your render can look something like this:
render() {
const { isFetching } = this.state;
return (
<div>
{isFetching ? (
<div>Loading...</div>
) : (
<div>
<p>
{this.state.phoneNumber} has {this.state.points} points...
</p>
<p>Would you like to redeem or add points?</p>
<div>
<button>Redeem Points</button>
<button>Add Points</button>
</div>
</div>
)}
</div>
);
}
React.StrictMode, makes it render twice, so that we do not put side effects in following locations
constructor
componentWillMount (or UNSAFE_componentWillMount)
componentWillReceiveProps (or UNSAFE_componentWillReceiveProps)
componentWillUpdate (or UNSAFE_componentWillUpdate)
getDerivedStateFromProps
shouldComponentUpdate
render
setState updater functions (the first argument)
All these methods are called more than once, so it is important to avoid having side-effects in them. If we ignore this principle it is likely to end up with inconsistent state issues and memory leaks.
React.StrictMode cannot spot side-effects at once, but it can help us find them by intentionally invoking twice some key functions.
These functions are:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
This behaviour definitely has some performance impact, but we should not worry since it takes place only in development and not in production.
credit: https://mariosfakiolas.com/blog/my-react-components-render-twice-and-drive-me-crazy/
it is done intentionally by react to avoid this
remove
<React.StrictMode> </React.StrictMode>
from index.js
I worked around this by providing a custom hook. Put the hook below into your code, then:
// instead of this:
useEffect( ()=> {
console.log('my effect is running');
return () => console.log('my effect is destroying');
}, []);
// do this:
useEffectOnce( ()=> {
console.log('my effect is running');
return () => console.log('my effect is destroying');
});
Here is the code for the hook:
export const useEffectOnce = ( effect => {
const destroyFunc = useRef();
const calledOnce = useRef(false);
const renderAfterCalled = useRef(false);
if (calledOnce.current) {
renderAfterCalled.current = true;
}
useEffect( () => {
if (calledOnce.current) {
return;
}
calledOnce.current = true;
destroyFunc.current = effect();
return ()=> {
if (!renderAfterCalled.current) {
return;
}
if (destroyFunc.current) {
destroyFunc.current();
}
};
}, []);
};
See this blog for the explanation.
Well, I have created a workaround hook for this. Check this, if it helps:
import { useEffect } from "react";
const useDevEffect = (cb, deps) => {
let ran = false;
useEffect(() => {
if (ran) return;
cb();
return () => (ran = true);
}, deps);
};
const isDev = !process.env.NODE_ENV || process.env.NODE_ENV === "development";
export const useOnceEffect = isDev ? useDevEffect : useEffect;
CodeSandbox Demo: https://github.com/akulsr0/react-18-useeffect-twice-fix
React internally monitors & manages its render cycles using its virtual dom and its diffing algorithms, so you need not worry about the number of re-renders. Let the re-renders to be managed by react. Even though the render function is getting invoked, there are sub components which doesn't gets refreshed on ui, if there is no props or state change inside it. Every setstate function call will inform react to check the diffing algorithm, and invoke the render function.
So in your case, since you have a setstate defined inside the getPoints function, it tells react to rerun the diffing process through the render function.

Can't perform a React state update on an unmounted component - React Native

I'm having this problem in this component, this is the code:
const AuthLoading = props => {
useEffect(() => {
AuthFlow();
});
const AuthFlow = () => {
if (!props.triedJWT) {
props.getUserToken();
return;
}
if (!props.jwt) {
props.navigation.navigate('Auth');
return;
}
if (!props.triedLogIn) {
props.getUserInfo();
return;
}
if (!props.user) {
props.navigation.navigate('Auth');
return;
}
props.navigation.navigate('App');
};
return (
<View style={styles.screen}>
<LoadingScreen />
</View>
);
};
How can I fix this?
The getUserInfoand getUserToken are connected to Redux actions and modify the jwt, triedJWF, triedLogin and user props. So when they are called, they re render the component.
Maybe this isn't the best way to do it? To make the action change state props so when they re render the if conditions are different? I also want to know if this is common practice or not.
EDIT: This is the warning I'm getting:
Thanks!

React.js "global" component that can be created multiple times

I can't get my head wrapped around this.
The problem: let's say there's an app and there can be some sort of notifications/dialogs/etc that i want to create from my code.
I can have "global" component and manage it, but it would limit me to only one notification at a time, this will not fit.
render() {
<App>
// Some components...
<Notification />
</App>
}
Or i can manage multiple notifications by the component Notification itself. But state management will not be clear.
The other problem if i have some sort of user confirmation from that component (if it's a confirmation dialog instead of simple notification) this will not be very convinient to handle with this solution.
The other solution is to render a component manually. Something like:
notify(props) {
const wrapper = document.body.appendChild(document.createElement('div'))
const component = ReactDOM.render(React.createElement(Notification, props), wrapper)
//...
// return Promise or component itself
}
So i would call as:
notify({message: '...'})
.then(...)
or:
notify({message: '...', onConfirm: ...})
This solution seems hacky, i would like to let React handle rendering, and i have an additional needless div. Also, if React API changes, my code breaks.
What is the best practice for this scenario? Maybe i'm missing something completely different?
You could use React Context for this.
You create a React context at a high level in your application and then associate a values to it. This should allow components to create / interact with notifications.
export const NotificationContext = React.createContext({
notifications: [],
createNotification: () => {}
});
class App extends Component {
constructor() {
super();
this.state = {
notifications: []
};
this.createNotification = this.createNotification.bind(this);
}
createNotification(body) {
this.setState(prevState => ({
notifications: [body, ...prevState.notifications]
}));
}
render() {
const { notifications } = this.state;
const contextValue = {
notifications,
createNotification: this.createNotification
};
return (
<NotificationContext.Provider value={contextValue}>
<NotificationButton />
{notifications.map(notification => (
<Notification body={notification} />
))}
</NotificationContext.Provider>
);
}
}
The notifications are stored in an array to allow multiple at a time. Currently, this implementation will never delete them but this functionality can be added.
To create a notification, you will use the corresponding context consumer from within the App. I have added a simple implementation here for demonstration purposes.
import { NotificationContext } from "./App.jsx";
const NotificationButton = () => (
<NotificationContext.Consumer>
{({ notifications, createNotification }) => (
<button onClick={() => createNotification(notifications.length)}>
Add Notification
</button>
)}
</NotificationContext.Consumer>
);
You can view the working example here.

How to make an API call on props change?

I'm creating a hackernews-clone using this API
This is my component structure
-main
|--menubar
|--articles
|--searchbar
Below is the code block which I use to fetch the data from external API.
componentWillReceiveProps({search}){
console.log(search);
}
componentDidMount() {
this.fetchdata('story');
}
fetchdata(type = '', search_tag = ''){
var url = 'https://hn.algolia.com/api/v1/search?tags=';
fetch(`${url}${type}&query=${search_tag}`)
.then(res => res.json())
.then(data => {
this.props.getData(data.hits);
});
}
I'm making the API call in componentDidMount() lifecycle method(as it should be) and getting the data correctly on startup.
But here I need to pass a search value through searchbar component to menubar component to do a custom search. As I'm using only react (not using redux atm) I'm passing it as a prop to the menubar component.
As the mentioned codeblock if I search react and passed it through props, it logs react once (as I'm calling it on componentWillReceiveProps()). But if I run fetchData method inside componentWillReceiveProps with search parameter I receive it goes an infinite loop. And it goes an infinite loop even before I pass the search value as a prop.
So here, how can I call fetchdata() method with updating props ?
I've already read this stackoverflow answers but making an API call in componentWillReceiveProps doesn't work.
So where should I call the fetchdata() in my case ? Is this because of asynchronous ?
Update : codepen for the project
You can do it by
componentWillReceiveProps({search}){
if (search !== this.props.search) {
this.fetchdata(search);
}
}
but I think the right way would be to do it in componentDidUpdate as react docs say
This is also a good place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed).
componentDidMount() {
this.fetchdata('story');
}
componentDidUpdate(prevProps) {
if (this.props.search !== prevProps.search) {
this.fetchdata(this.props.search);
}
}
Why not just do this by composition and handle the data fetching in the main HoC (higher order component).
For example:
class SearchBar extends React.Component {
handleInput(event) {
const searchValue = event.target.value;
this.props.onChange(searchValue);
}
render() {
return <input type="text" onChange={this.handleInput} />;
}
}
class Main extends React.Component {
constructor() {
this.state = {
hits: []
};
}
componentDidMount() {
this.fetchdata('story');
}
fetchdata(type = '', search_tag = '') {
var url = 'https://hn.algolia.com/api/v1/search?tags=';
fetch(`${url}${type}&query=${search_tag}`)
.then(res => res.json())
.then(data => {
this.setState({ hits: data.hits });
});
}
render() {
return (
<div>
<MenuBar />
<SearchBar onChange={this.fetchdata} />
<Articles data={this.state.hits} />
</div>
);
}
}
Have the fetchdata function in the main component and pass it to the SearchBar component as a onChange function which will be called when the search bar input will change (or a search button get pressed).
What do you think?
Could it be that inside this.props.getData() you change a state value, which is ultimately passed on as a prop? This would then cause the componentWillReceiveProps function to be re-called.
You can probably overcome this issue by checking if the search prop has changed in componentWillReceiveProps:
componentWillReceiveProps ({search}) {
if (search !== this.props.search) {
this.fetchdata(search);
}
}

window.postMessage not working in componentDidMount without setTimeOut in React

I'm working on passing data between a react webpage and a react-native webview. I want to send a signal to the react-native webview on the mobile once the webpage has been loaded.
I wonder why on the react webpage, the window.postMessage() doesn't work unless it's used with setTimeout. There's no error at all in the console, but it has to be delayed for about 500 in order to work. Can anyone explain that? I prefer avoiding setTimeout because it feels unreliable.
#observer
class TalkToMobile extends React.Component {
#observable message;
render() {
return (
<div>
{
message ?
<Editor data={this.message}/>: null
}
</div>
)
}
componentDidMount() {
document.addEventListener('message', (e: any) => {
if (e.data) {
this.message = JSON.parse(e.data);
}
});
let payload = {};
payload.command = "giveMeData";
window.postMessage(JSON.stringify(payload), "*")
/*
setTimeout(()=>{
let payload = {};
payload.command = "giveMeData";
window.postMessage(JSON.stringify(payload), "*")
}, 500);*/
}
}
You probably need to wait for the webview to load before posting messages to it, so it makes sense to do it from within the webview onLoad callback. Try the following:
...
onWebViewLoaded() {
if (this.webview) {
this.webview.postMessage('');
}
}
render() {
return (
<WebView
ref={webview => { this.webview = webview }}
source={{uri: 'https://facebook.github.io/react/'}}
onLoad={this.onWebViewLoaded}
/>
)
}
...
If you want to post message from webview to the app, try handling it in componentDidUpdate. By the time it gets fired the DOM is loaded and there will be no need for setTimeout

Categories