Reputation: 69
I'm new to both Javascript and React Native and I need some help working out how I can trigger a useEffect from an external function.
My application is listening for messages from Firebase Cloud Messaging (FCM) with the react-native-firebase library. On receipt, a background handler writes the message to asyncstorage and then displays a notification with notifee library.
When the application is opened, it retrieves the messages from asyncstorage and renders them using FlatList. This works perfectly when the application is first opened and the list renders.
The problem is that if a message is received while the app is in the foreground, the handler correctly processes the incoming message and displays the notification, but it cannot trigger a refresh of the message list which has already been rendered.
How can I trigger a refresh/re-run of the useEffect from the foregroundMessageHandler?
This is the async function I'm using to capture the FCM message while the app is in the foreground:
index.js
const foregroundMessageHandler = messaging().onMessage(async remoteMessage => {
await _handleNewMessage(remoteMessage); // Inserts FCM message into asyncstorage
await onDisplayNotification(remoteMessage); // Displays notification
// ...how to trigger a refresh of the useEffect from here??
});
And this is how I'm rendering the messages from asyncstorage in the UI:
app.js
function HomeScreen() {
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState(false);
useEffect(() => {
// Function to retrieve historical messages from AsyncStorage
async function _retrieveData() {
try {
const value = await AsyncStorage.getItem('messages');
if (value !== null) {
const oldMessages = JSON.parse(value);
setData(oldMessages);
setLoading(false);
} else {
setData(false);
setLoading(false);
}
} catch (error) {}
}
_retrieveData(); // Retrieve old messages from asyncstorage
},[]);
function renderMessagesFlatList(data) {
if (isLoading) {
return (
<ActivityIndicator />
);
} else if (data) {
return (
<FlatList
data={data}
keyExtractor={(item, index) => item.id}
renderItem={({ item }) => (
<>
<View style={{ flexDirection: 'row', flex: 1 }}>
<Image
style={{ width: 40, height: 40 }}
source={{uri:item.icon}}
/>
<View style={{ flex: 1, paddingLeft: 5 }}>
<Text style={{ color: '#000000', fontWeight: 'bold' }}>{item.sender}</Text>
<Text>{timeSince(item.time)} ago</Text>
</View>
</View>
<Text style={{ paddingTop: 4 }}>{item.body}</Text>
<Divider style={{ backgroundColor: '#e7e5e5', height: 2, marginTop: 5, marginBottom: 5 }} />
</>
)}
/>
);
} else {
return (
<Text>No notifications to display</Text>
);
};
};
Update: Using the code below I've managed to refresh the FlatList component when a message is received. But it seems every time the useEffect fires it creates another foreground listener which results in duplicate notifications for subsequent messages. I need to work out how to stop these duplicated foreground listeners from being created.
function HomeScreen() {
const [data, setData] = useState([]);
const [refreshPage, setRefreshPage] = useState(false);
useEffect(async () => {
const value = await AsyncStorage.getItem('messages35');
// ...functions to retrieve and sort historical messages into array "sortedInput"
setData(sortedInput); // setState to provide initial data for FlatList component
};
const unsubscribeOnMessage = messaging().onMessage(async remoteMessage => {
// Function to process new message and insert into local storage
await _handleNewMessage(remoteMessage);
// Display notification to user
onDisplayNotification(remoteMessage);
// Trigger refresh of FlatList component with setState
setRefreshPage(Math.random() * 100);
});
return () => {
unsubscribeOnMessage();
};
}, [refreshPage]);
Upvotes: 1
Views: 1151
Reputation: 2459
There's multiple solutions for your problem, but here's how I would prefer to solve it.
You can add another messaging().onMessage
listener to your HomeScreen
component and whenever a new message is received, you simply refresh your list by retrieving the data again:
import messaging from '@react-native-firebase/messaging';
function HomeScreen() {
// ...
useEffect(() => {
// Function to retrieve historical messages from AsyncStorage
async function _retrieveData() {
try {
const value = await AsyncStorage.getItem('messages');
if (value !== null) {
const oldMessages = JSON.parse(value);
setData(oldMessages);
setLoading(false);
} else {
setData(false);
setLoading(false);
}
} catch (error) {}
}
_retrieveData(); // Retrieve old messages from asyncstorage
// Foreground push notifications listener
const unsubscribeOnMessage = messaging().onMessage(() => {
// Whenever a new push notification is received, call the _retrieveData function again
_retrieveData();
});
return () => {
unsubscribeOnMessage();
};
}, []);
// ...
}
UPDATE
foregroundMessageHandler
out of your HomeScreen
component (your initial code). Having multiple messaging.onMessage
listeners is valid.const [refreshPage, setRefreshPage] = useState(false);
you can just use setData
in your messaging.onMessage
listener in HomeScreen
component. So the solution could be something like this:index.js
const foregroundMessageHandler = messaging().onMessage(async remoteMessage => {
await _handleNewMessage(remoteMessage); // Inserts FCM message into asyncstorage
await onDisplayNotification(remoteMessage); // Displays notification
// ...how to trigger a refresh of the useEffect from here??
});
app.js
import messaging from '@react-native-firebase/messaging';
function HomeScreen() {
// ...
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState(false);
useEffect(() => {
// ... Other code
const unsubscribeOnMessage = messaging().onMessage((remoteMessage) => {
// use setData to update your data for FlatList. It could be used to just append the new message to the current data
setData((data) => [...data, remoteMessage]);
});
return () => {
unsubscribeOnMessage();
};
// There shouldn't be any dependencies here
}, []);
// ...
}
Upvotes: 1