Socket IO in React Native firing multiple events - react-native

I have been reading similar topics but I did not really get the their solutions. I have read that the cause of this behavior is if you are re-registering a new event handler. In my code I am not sure which part I am re-registering a new event handler.
SERVER
socket.on('test', function (data) {
socket.emit('test2', data);
});
CLIENT
save() {
app.io.emit('test', { hello: 'world' });
app.io.on('test2', (res)=>{
if(Object.keys(res).length > 0) {
console.log(res);
}
})
}
render() {
return (
<View style={styles.container}>
<Button title='Save' onPress={this.save}></Button>
</View>
);
}
The problem here is this, when I click the button SAVE, the number of return that the function app.io.on is throwing keeps bumping up.
Ex:
First click return once
Second click return twice
Third click returns 3x
Is there a way to prevent this from happening like it should only return once? Thanks in advance.

Anyways, I sorted it out by moving the code
app.io.on('test2', (res)=>{
if(Object.keys(res).length > 0) {
console.log(res);
}
})
on the componentDidMount() so it will called once. The event will keep on bumping up (firing multiple times) everytime this function is called which is the caused of my problem.

Related

ReactNative UI freezing for a second before rendering a component with a fetch in useEffect()

TL;DR: My UI freezes for .5-1s when I try to render a component that does a API fetch within a useEffect().
I have ComponentX which is a component that fetches data from an API in a useEffect() via a redux dispatch. I'm using RTK to build my redux store.
function ComponentX() {
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(fetchListData()); // fetch list data is a redux thunk.
}, [dispatch]);
...
return <FlatList data={data} /> // pseudo code
}
as you can see the fetch will happen everytime the component is rendered.
Now I have ComponentX in App along with another component called ComponentY.
Here's a rudamentary implementation on how my app determines which component to show. Pretend each component has a button that executes the onClick
function App() {
const [componentToRender, setComponentToRender] = useState("x");
if (componentToRender === "x") {
return <ComponentX onClick={() => setComponentToRender("y")}/>
} else {
return <ComponentY onClick={() => setComponentToRender("x")}/>
}
}
Now the issue happens when I try to move from ComponentY to ComponentX. When I click the "back" button on ComponentY the UI will freeze for .5-1s then show ComponentX. Removing the dispatch(fetchListData()); from the useEffect fixes the issue but obviously I can't do that since I need the data from the API.
Another fascinating thing is that I tried wrapping the dispatch in an if statement assuming that it would prevent a data fetch thus resolving the "lag" when shouldReload is false. The UI still froze before rendering ComponentX.
useEffect(() => {
if (shouldReload) { // assume this is false
console.log("reloading");
dispatch(fetchListData());
}
}, [dispatch, shouldReload]);
Any idea what's going on here?
EDIT:
I've done a little more pruning of code trying to simplify things. What I found that removing redux from the equation fixes the issue. By simply doing below, the lag disappears. This leads me to believe it has something to do with Redux/RTK.
const [listData, setListData] = useState([]);
useEffect(() => {
getListData().then(setListData)
}, []);
Sometimes running the code after interactions/animations completed solves the issue.
useEffect(() => {
InteractionManager.runAfterInteractions(() => {
dispatch(fetchListData());
});
}, [dispatch]);

How do I call functions inside render() method and still keep it pure?

I want to use a modal in my React Native app that ask the user to confirm his action.
The state looks like this:
state = {
dialogVisible: false,
confirmed: null
}
If the user confirms his delete action (turning confirmed to true), I want to execute my delete() method.
Delete method:
delete = () => {
const { deckName } = this.props.navigation.state.params
console.log('WAS CONFIRMED')
this.setState({
dialogVisible: false
})
this.props.navigation.navigate('Decks')
removeDeckFromStorage(deckName)
this.props.dispatch(removeDeck(deckName))
this.setState({
confirmed: null
})
}
noDelete = () => {
this.setState({
dialogVisible: false
})
this.setState({
confirmed: null
})
}
When the user confirmed his action, the modal closes, and the delete is done. Afterwards, I want to set confirmed back to null re-use it later.
On the other hand, if the user does not confirm the modal by clicking No, the noDelete() method should be called, which just closes the modal and sets confirmed back to null.
My problem is now that I get a warning saying:
Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
That is because I check for the state of confirmed inside the render() method:
const { confirmed } = this.state
if (confirmed){
this.delete()
}
else if (confirmed === false){
this.noDelete()
}
I did this because when I checked the state of confirmed inside the delete method right after setting the confirmed state to true, it always said null.
I put the check inside render because after the confirmed state is changed through the user input, the component is re-rendered and thus giving me the right state for the query.
The dialog buttons each change the current confirmed state when clicked:
positiveButton={{
title: "YES",
onPress: () => this.setState({confirmed: true})
}}
So, how can I check for confirmed after it was set but still outside of the render method to keep it pure?
You should never update the state in the render method. You should move your logic to the delete and noDelete functions instead.
I'm not sure how your modal is, however let's suppose it's something like this:
<View>
<TouchableOpacity onPress={this.delete}>
<Text>Delete</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.noDelete}>
<Text>No Delete</Text>
</TouchableOpacity>
</View>
And in the delete and noDelete you simply remove the setState({ confirmed }) since you're already calling the deletion from there.

"Maximum update depth exceeded ..." when parent callback is called in React-Native

I still don't get why I'm getting an update depth exceeded on this specific case, i would appreciate if any help is given.
To give some insight.
I got a Parent and a Child (multi-list selector) component that I created.
This multi-list allows you to select multiple items and when modal is closed, I execute a callback that comes from Parent, with the list of items selected and update it from the Parent itself by doing setState.
On Parent's constructor I got the callback function like this:
Parent
this.onItemSelected = this.onItemSelected.bind(this);
This is the function above:
onItemSelected(updatedList) {
this.setState({selectedItemList: updatedList});
}
This is when I use it and pass it to Children.
renderModalMultiSelect = () => {
return (
<MultiSelect
items={this.state.itemList}
onItemSelect={ this.onItemSelected }
disabled={this.state.filters.type === 'all'}
isLoadingList={this.state.isLoadingList}
/>
)
}
Of course this is on Parent's render() function as { this.renderModalMultiSelect() }
Children
On this component, I handle the selected item list locally, is not on the State, so whenever I press on items, I just simply do a push on this local array.
When I close the modal, I run the parent props callback.
onClose = () => {
this.setState({ modalOpened: false }, () => {
// Optional callback
if (this.props.onItemSelect) {
this.props.onItemSelect(this.selectedItems);
}
})
}
Again, this function is used on children's render()
<Button onPress={ this.onClose } transparent>
<Icon name="md-arrow-back" style={ styles.headerIcon }/>
</Button>
Before this, I was passing a state from Parent to Children with the list of selected items, but it didn't seem right to do things like: this.props.selectedItemList.push(item) or is this fine to do?
Thanks and I hope I can understand what's going on here, it keeps throwing this error whenever I close the modal (onClose is called).

React native pass property for async

So I got an array with a ton of fields that are bound to <Text>
Now I also have a button in here. What I want to do is onclick I want to go to a function onSubmit which also takes the value session.gameId and puts it in asyncstorage.
{this.state.sessions.map((session, index) => {
return (
<View style={styles.cardContainer}>
<Text style={styles.title} >{session.title}</Text>
<Text style={styles.text}>Organisator: {session.organizer}</Text>
<Text style={styles.text}>Hoofd Thema: {session.mainTheme}</Text>
<Text style={styles.text}>Aantal Deelnemers: {session.numberParticipants}</Text>
<Text style={styles.text}>Game Id: {session.gameId}</Text>
<TouchableOpacity style={styles.buttonContainer} >
<Text style={styles.buttonText} onPress={this.onSubmit} value={session.gameId} >
Selecteer
</Text>
</TouchableOpacity>
But I have no idea how my onsubmit can handle both an event to change page after storing the value into asyncstorage
Please help
Not sure if I'm understanding your question correctly, but if you're trying to store the value of session.gameId into AsyncStorage and then change the page, your function may look something like this:
changePage() {// functionality to navigate to another page}
/*
* have onSubmit be an async function so you use the 'await' keyword
* await forces an asynchronous line of code to wait until the operations is done before moving forward
*/
async onSubmit(gameId) {
try {
await AsyncStorage.setItem('#store:key', gameId)
} catch(error) {
// handle error
}
// however you are changing page, handle it here
// this code wont run until gameId has been stored in async storage
this.changePage()
}
You would also need to pass the gameId to the function to actually call it now:
onPress={() => this.onSubmit(session.gameId)}
Take a look at how async functions can make your life easier :)
I'm answering this assuming that when you say your onSubmit triggers "an event to change page", you mean that it navigates to another screen.
If so, you seem to be asking for something like this:
onSubmit = async gameId => {
try {
await AsyncStorage.setItem('gameId', gameId)
// Success: navigate away here
this.props.goToMyOtherScreen()
} catch {
// Handle error
}
}
To get gameId into your submit handler, you could use an inline anonymous function:
<Text
style={styles.buttonText}
onPress={() => this.onSubmit(session.gameId)}>

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!