How to receive one callback from three simultaneous events in React Native? - react-native

I'm listening to three different events that happen simultaneously and I want to get some data from each of these events. However, I want to receive the latest data from all three events at once.
I tried using useEffect but, of course, this is triggering the callback at least three times, instead of just once.
const [key, setKey] = useState('');
const [text, setText] = useState('');
const [position, setPosition] = useState(0);
const onKeyPress = ({ nativeEvent: { key } }) => setKey(key);
const onChangeText = text => setText(text);
const onSelectionChange = ({ nativeEvent: { selection: { start, end } } }) => {
start === end && setPosition(start);
}
useEffect(() => {
// I want to do stuff with the latest key, text and position.
// However, this is called more than once when typing.
}, [key, text, position]);
// ..
<TextInput
onChangeText={onChangeText}
onKeyPress={onKeyPress}
onSelectionChange={onSelectionChange}
/>
How can I achieve this?

Related

Expo-notifications trigger all useEffects in the application

I created the entire flow for expo-notifications, although I encounter one problem. Once I receive the notification, the UI of the specific type is re-rendered and - which is the core of the problem - all the useEffects with fetch get triggered in the application; it seems that it re-renders the entire application. Even disabling the update of the specific part of the UI (that I want to update) still causes that a notification makes the app to re-render.
I tried to find the cause of that, but no progress so far. Did anyone of you ever encountered this kind of problem? Why the app gets re-rendered entirely?
The function registerForPushNotificationsAsync is copy-pasted from their docs.
Here is my notification provider - I get notification correctly, but idk what causes the re-render and trigger all the useEffects:
const NotificationsProvider = () => {
const authenticationStatus = useSelector(authStatus);
const dispatch = useDispatch();
const [expoPushToken, setExpoPushToken] = useState("");
const [notification, setNotification] = useState<Notifications.Notification | null>(null);
useEffect(() => {
if (authenticationStatus === AUTHENTICATION_MESSAGES.AUTHENTICATION_SUCCESS) {
registerForPushNotificationsAsync()
.then((token) => setExpoPushToken(token))
.catch((error) => console.error(error));
const subscription = Notifications.addNotificationReceivedListener((receivedNotification) => {
setNotification(receivedNotification);
const { id, title } = receivedNotification.request.content.data;
console.log(receivedNotification.request.content.data);
dispatch(
addAsync(
[
{
id: id,
title: title,
},
],
1 * 1000
)
);
});
APP.tsx
const App = () => {
const [fontsLoaded] = useFonts({
Roboto_400Regular,
Roboto_500Medium,
});
return fontsLoaded ? (
<Provider store={store}>
<PaperProvider theme={theme}>
<NotificationsProvider />
</PaperProvider>
</Provider>
) : (
<AppLoading />
);
};

useQuery renders when state changes

I want to useQuery renders whenever the state changes
is there any option in useQuery hook
`export const Search = ({ navigation }) => {
const [search, setSearch] = useState();
const [dismiss, setDismiss] = useState(false);
const [searchResult, setSearchResult] = useState();
const searchHander = (query) => {
setSearch(query)
setDismiss(true)
}
const searching = useQuery(['searching', search], () => api.search(search));
useMemo(() => {
setSearchResult(searching?.data ? searching?.data?.results : []);
}, [searching?.data])
const searchResults = ({ item }) => {
return <View style={{ marginVertical: 10 }}><SearchResults navigation={navigation} data={item} /></View>
}
const desmiss = useRef(null);
return (...)}`
useQuery is not depend to state
I don't fully understand the question:
I want to useQuery renders whenever the state changes
I'm just assuming you want to refetch when the state changes, because when state changes, your component does render.
For this, all you need to do is add search to the queryKey, which you have already done.
Further, I can see that you are calling setSearchResults in useMemo which is a) unnecessary because react-query already gives you the result and b) violates component purity because you call setState during render.
I think the component should just be:
const [search, setSearch] = useState();
const [dismiss, setDismiss] = useState(false);
const [searchResult, setSearchResult] = useState();
const searchHander = (query) => {
setSearch(query)
setDismiss(true)
}
const searching = useQuery(['searching', search], () => api.search(search));
const searchResult = searching?.data?.results ?? []
Then you can work with searchResult, and whenever setSearch is called, your query will refetch.

How to hide element when device keyboard active using hooks?

I wanted to convert a hide element when keyboard active HOC I found to the newer react-native version using hooks (useEffect), the original solution using the older react lifecycle hooks looks like this - https://stackoverflow.com/a/60500043/1829251
So I created a useHideWhenKeyboardOpen function that wraps the child element and should hide that child if the device keyboard is active using useEffect. But on render the child element useHideWhenKeyboardOpen isn't displayed regardless of keyboard displayed.
When I've debugged the app I see the following error which I didn't fully understand,because the useHideWhenKeyboardOpen function does return a <BaseComponent>:
ExceptionsManager.js:179 Warning: Functions are not valid as a React
child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than
return it.
in RCTView (at View.js:34)
Question:
How can you attach keyboard displayed listener to a component in the render?
Example useHideWhenKeyboardOpen function:
import React, { useEffect, useState } from 'react';
import { Keyboard } from 'react-native';
// Wrapper component which hides child node when the device keyboard is open.
const useHideWhenKeyboardOpen = (BaseComponent: any) => (props: any) => {
// todo: finish refactoring.....
const [isKeyboadVisible, setIsKeyboadVisible] = useState(false);
const _keyboardDidShow = () => {
setIsKeyboadVisible(true);
};
const _keyboardDidHide = () => {
setIsKeyboadVisible(false);
};
/**
* Add callbacks to keyboard display events, cleanup in useeffect return.
*/
useEffect(() => {
console.log('isKeyboadVisible: ' + isKeyboadVisible);
Keyboard.addListener('keyboardDidShow', _keyboardDidShow);
Keyboard.addListener('keyboardDidHide', _keyboardDidHide);
return () => {
Keyboard.removeCurrentListener();
};
}, [_keyboardDidHide, _keyboardDidShow]);
return isKeyboadVisible ? null : <BaseComponent {...props}></BaseComponent>;
};
export default useHideWhenKeyboardOpen;
Example Usage:
return(
.
.
.
{useHideWhenKeyboardOpen(
<View style={[styles.buttonContainer]}>
<Button
icon={<Icon name="save" size={16} color="white" />}
title={strings.STOCKS_FEED.submit}
iconRight={true}
onPress={() => {
toggleSettings();
}}
style={styles.submitButton}
raised={true}
/>
</View>,
)}
)
Mindset shift will help: think of hooks as data source rather than JSX factory:
const isKeyboardShown = useKeyboardStatus();
...
{!isKeyboardShown && (...
Accordingly your hook will just return current status(your current version look rather as a HOC):
const useHideWhenKeyboardOpen = () => {
const [isKeyboadVisible, setIsKeyboadVisible] = useState(false);
const _keyboardDidShow = useCallback(() => {
setIsKeyboadVisible(true);
}, []);
const _keyboardDidHide = useCallback(() => {
setIsKeyboadVisible(false);
}, []);
useEffect(() => {
Keyboard.addListener('keyboardDidShow', _keyboardDidShow);
Keyboard.addListener('keyboardDidHide', _keyboardDidHide);
return () => {
Keyboard.addListener('keyboardDidShow', _keyboardDidShow);
Keyboard.addListener('keyboardDidHide', _keyboardDidHide);
};
}, [_keyboardDidHide, _keyboardDidShow]);
return isKeyboadVisible;
};
Note usage of useCallback. Without it your hook will unsubscribe from Keyboard and subscribe again on every render(since _keyboardDidHide would be referentially different each time and would trigger useEffect). And that's definitely redundant.

React native - how to refresh screen when message has been sent?

so currently I have 2 simulators open. Each simulator is logged in as a different user. When I enter the chat between these 2 users, messages are getting sent live in the socket which is perfect.
My problem:
Example: if user 1 is on the all messages screen and user 2 is inside the chat. And user 2 sends user 1 a message, user 1's screen does not automatically update with the new message, I need to either scroll to refresh or navigate from one page to the other.
How should I implement the when a new message is sent it gets shown to user 1?
Here is my code:
AllMessagesScreen.js
const [posts, setPosts] = useState([]);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const[page,setPage]=useState(0);
const [refreshing, setRefreshing] = useState(false);
const loadPosts = async () => {
setLoading(true);
const response = await messagesApi.getMessages();
setLoading(false);
if(refreshing) setRefreshing(false);
if (!response.ok) return setError(true);
setError(false);
setPosts(response.data)
};
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
loadPosts();
});
return unsubscribe;
}, [navigation]);
return(
<FlatList
data={posts}
keyExtractor={(listing) => listing.id.toString()}
renderItem={({ item,index }) => (
<MessagesList
title={item.Post.title}
subTitle={item.Messages[0].message}
onPress={() => navigation.navigate(routes.CHAT,{message:item,index})}
/>
)}
ItemSeparatorComponent={
ListItemSeparator
}
refreshing={refreshing}
onRefresh={() => {
loadPosts()
}}
/>
In other words, when a message is sent in the chat screen I want my allmessages screen to re-render, currently it is only getting render when I scroll to refresh or navigate to allmessages screen.
If you require any additional information please tell me, I will provide it immediately. Thank you
chat screen.js
function ChatScreen({route,navigation}) {
const message = route.params.message;
const [messages, setMessages] = useState([]);
const [refreshing, setRefreshing] = useState(false);
const [text, setText] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [socket, setSocket] = useState(null);
const { user } = useAuth();
const index = route.params.index;
const updateView = route.params.updateView;
useEffect(() => {
const newsocket =io.connect("IPADDRESS")
setMessages(message.Messages)
newsocket.on('connect', msg => {
console.log(`user: ${user.id} has joined conversation ${message.id}`)
setSocket(newsocket)
newsocket.emit('subscribe', message.id);
});
newsocket.on("send_message", (msg) => {
console.log("this is the chat messages:", msg);
setMessages(messages => [msg, ...messages]);
});
return(()=>newsocket.close());
}, []);
Let's say you have the groups schema like this:
{
id: string;
name: string;
}
and messages schema like this:
{
id: string;
groupId: string;
text: string;
}
Now to recognise that a new message has been sent in a group you can add lastMessage in group schema, like this:
{
id: string;
name: string;
lastMessage: Message;
}
This way your whenever a new message is sent, respective group will change, which will make the web-socket emit an event and you will get updated data.
In you db in lastMessage field you can have foreign key to message collection, but when sending the json to frontend send the whole message object.
Also you will have to update your function for message creation to update the group too with lastMessage.
If you have creation time in messages schema, having lastMessage in group will help with sorting them too, like showing group with latest message on top.

Handle component remounting with react-navigation (same implementation, different result)

I've got a problem which I cannot seem to solve.
I have an app built with react-native and expo and react-navigation.
What I am trying to achieve:
A user can sign up for an event. All events the user wants to participate are listed on one screen. Pressing on one marks this event as "active" - The user can (on another screen) now see details and a bulletin board for this event.
Hierarchy:
- Loginstack (not relevant, just for completion)
- Homestack
-- Home
-- Eventstack (Active Event)
--- Details
--- Bulletin Board
-- Create Event
-- Sign up for Event
-- List Events User goes to
From the Sign up Screen I navigate to the Eventstack while first add the Event to local storage. The Details Screen now checks for the active event and loads the data. When the user navigates away from this screen and back to it, the Details screen should only reload the data if the active event has changed! First, the Stack Navigation:
<Stack.Navigator initialRouteName="EventDetails" headerMode="none" screenOptions={({ route, navigation}) => ({
animationEnabled: false,
})}>
<Stack.Screen
name="EventDetails"
component={EventDetailsScreen}
initialParams={{ active: EventFooter.details, }}
/>
<Stack.Screen
name="Bulletin"
component={BulletinScreen}
initialParams={{ active: EventFooter.bulletin, }}
/>
</Stack.Navigator>
My Code:
const EventDetailsScreen = ({ navigation }) => {
const [isLoading, setLoading] = useState(true);
const [eventToLoad, setEventToLoad] = useState(null);
const [event, setEvent] = useState(null);
const userData = useContext(UserContext);
const _loadEvent = async (eId?: number) => {
setLoading(true);
let eventId: number = eId || await getActiveEvent();
if (eventId == null) {
if (userData.user.event.id === undefined) {
return;
}
eventId = userData.user.event.id;
}
const eventToSet = await findEventById(eventId);
setEvent(eventToSet);
setLoading(false);
};
const _checkEvent = async () => {
const actEvent = await getActiveEvent();
setEventToLoad(actEvent);
}
useFocusEffect(
useCallback(() => {
_checkEvent();
}, [])
);
useEffect(() => {
_loadEvent();
}, [eventToLoad]);
return ( ... );
};
For this code the following is happening. The first time the user navigates to the Details Screen the Event Details are loaded from the server via useEffect. When I now navigate away and back to this screen useFocusEffect would set the Active Event Id. If it is the same as before, useEffect will not be fired... Working. Logs are saying the same. The first time the active event is set, for all next navigations to the details sreen the already set data is used. Now for the Bulletin Board (same Hierarchy level) I implemented it the same way.
const BulletinScreen = ({ navigation }) => {
const [isLoading, setLoading] = useState(false);
const [eventToLoad, setEventToLoad] = useState(null);
const [threads, setThreads] = useState<Array<Thread>>(null);
const userData = useContext(UserContext);
const _getThreads = async () => {
setLoading(true);
let eventId: number = await getActiveEvent();
if (eventId == null) {
if (userData.user.event.id === undefined) {
return;
}
eventId = userData.user.event.id;
}
const threadsToSet = await getThreads(eventId, userData.user);
setThreads(threadsToSet);
setLoading(false);
};
const _getEvent = async () => {
const actEvent = await getActiveEvent();
setEventToLoad(actEvent);
}
useFocusEffect(
useCallback(() => {
_getEvent();
}, [])
);
useEffect(() => {
_getThreads();
}, [eventToLoad]);
return ( ... );
};
This time, every time I navigate to the bulletin screen the initial states (null) are used and therefore trigger a reload, which is not what I want.
I use a stack navigation and native-base for a footer tab navigation, which is used to navigate between the Details and Bulletin Screen. Do I have a blind thinking spot? It is the same implementation, right? Why does it behave differently?
Follow-up: Stack Navigation just works that way... Tab Navigation does not. So it works with a Tab Navigation.