notifications doesn't close when route changes antd - reactjs

In my react app I need to close all notifications on current tab when i go to another tad. antd version "^4.20.7". Notifications open and close through antd api.
I know that i can do it directly in the component, but maybe is it some solutions to add this functionality to all notifications in whole app?

You can use the destroy method to get rid of all notification generated.
In your case you would just need to notification.detroy() when another tab is clicked.
To add this functionality to all notification that exists inside your app you would simply need to handle the notification state in redux or equivalent.

Resolved by creating a hook that monitors the change of the route and inserting it in the App component and wrapper class Notification that store notification key. I used close method instead of destroy because when using destroy notification disappear without close animation.
class Notification {
key = '';
constructor() {
notification.config({
closeIcon: <CrossIcon />,
maxCount: 1,
});
}
open(config: ArgsProps) {
this.key = `notification-${Date.now()}`;
notification.open({
...config,
key: this.key,
});
}
close() {
notification.close(this.key);
}
}
function useNotificationClose() {
const {pathname} = useLocation(); // React Router v6 hook
useEffect(() => {
Notification.key && Notification.close();
}, [pathname]);
}
function App() {
useNotificationClose();
return ...your app components
}
Note: this will work when maxCount set to 1 as in my case.

Related

Trigger an analytics event when user see specific screen

DESCRIPTION
I have an app that should trigger an analytics event just once at a specific screen when a component is shown to the user.
i.e: if the user open the application and land to home screen and the component is showing, then the analytics event should be sent. If the user navigate to Settings screen and the component is there, then the event should trigger, but if the user navigate back to home screen, the event shouldn't trigger. Just have to be called once per screen.
If the user quit the app completely, the event should fire again once reopen it
STACK
react-native
react-navigation
react-redux
WHAT I'VE DONE
I've tried to use the useIsFocused hook with useEffectand useRef but it turns that when the screen is rerender for an async fetch event, the state inside my useEffect is reset and the analytics event trigger again, which is not the expected behavior.
const isHasBeenFocused = useRef()
const isFocused = useIsFocused()
useEffect(()=>{
if(isHasBeenFocused.current){
return;
}
isFocused && dispatch(analyticsEvent, payload)
isHasBeenFocused.current = isFocused
}, [])
This is my approach, but I don't know if it's the correct one or if there's another, better option.
Hope anyone could help me on this.
If you want the analytics event to fire only once for the lifetime of the app installation, you could use AsyncStorage for flags like this. AsyncStorage used to be a core RN module but is now community-based.
This will persist until the app is uninstalled.
useEffect(() => {
const check = async () => {
const hasReported = await AsyncStorage.getItem('yourKey');
if (hasReported) return;
AsyncStorage.setItem('yourKey', true);
dispatch(analyticsEvent, payload);
};
check();
}, []);
If you want the analytics to fire once per session, you could use the same solution with an AppState listener to reset the value to something falsy when the app is backgrounded. There's a good example of using AppState in the React Native docs.
If you need the value to persist across app installs, the best solution is to store it in your backend.

How to re-render or refresh previous screen when going back using react router flux

In my react native app I am using react native router flux, while going back to the previous screen I want the previous screen to re-render or refresh. I am using Action.pop() to go back to the previous. How I can refresh the previous screen?
You need to listen "focus" event on navigation object on your screen and add the logic for fetching new data inside the event handler.
function YourScreenComponent({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// fetch new data here
});
return unsubscribe;
}, [navigation]);
return <SomeContent />;
}
I solved it using Action.reset('key of the screen') of react router flux

React Navigation - Get next screen name for another navigator

I have React Native app with React Navigation. I want to log screen name every time when it changes.
In my app I already have several stackNavigator's and one switchNavigator to combine them.
Now I've just added next onTransitionStart property to all stackNavigator's:
export default createStackNavigator(
{
...
},
{
...
onTransitionStart: transitionProps => {
console.log('move to: ', transitionProps.scene.route.routeName);
},
},
);
Mostly it helps and do it just I wanted. However, it doesn't work in cases when I move from one stackNavigator to another. Where it is a better way to leave the callback to know that we moved to another stackNavigator and get its first screen?
You can actually try the below method:
import { NavigationEvents } from 'react-navigation';
render() {
<NavigationEvents
onWillFocus={() => {
console.log('screenA')
}}
/>
}
Quoted from documentation
NavigationEvents is a React component providing a declarative API to subscribe to navigation events. It will subscribe to navigation events on mount, and unsubscribe on unmount.
For me helped next one: createAppContainer creates the container with onNavigationStateChange property. It takes a function with previous and next navigation state as arguments. Both of them have enough data to understand which screen was previous and which will be next one. Since I have this app container in my main App component, it looks next way:
<View>
/* ... */
<RootNavigator
/* ... */
onNavigationStateChange={(prevState, newState, action) => {
if (action.type === 'Navigation/NAVIGATE' || action.type === 'Navigation/BACK') {
const currentNavigator = newState.routes[newState.index];
const screenName = currentRoute.routes[currentNavigator.index].routeName;
console.log('Current screen is: ', screenName);
}
};}
/>
/* ... */
</View>
Both state objects store the index and routes properties. They represent chosen stackNavigator index and the list of stackNavigator's respectively. So we can get the number of chosen navigator and get the index of current route for it.

React-Router: Apply logic to component before navigating to a different route

Is it possible to apply logic to a component before navigating to a different route?
For example, my component looks something like this:
class Example extends React.Component {
//Handles logic for when user leaves page
handlePageExit = () => {
console.log("Leaving page...");
}
//Set onBeforeUnload event listener so handlePageExit function is called when user closes or refreshes page
componentDidMount = () => {
window.addEventListener("onbeforeunload", this.handlePageExit);
}
//This hook is called when page exits or is refreshed
//It will remove event listener when
//However, it wont be called when user changes to a new route
componentWillMount = () => {
window.removeEventListener("onbeforeunload", this.handlePageExit)
}
//There's no react life cycle hook I can use to call HandlePageLogic when user navigates to a new route to remove event listener or apply other logic...
render(){
return(
<div /> //Not relevant to question
)
}
}
I'm trying to apply logic such as removing event listeners before the page is navigated to the new route.
I've tried using life cycle methods such as componentWillReceiveProps, componentWillUnmount, and componentShouldUpdate to insert logic before the component is unmounted however they don't seem to be invoked when navigating to a new page.
Does anyone know where or how I can insert logic in between a route change in react-router v4?
Thank you.
Yes, it is.
You need to add a change listener to you react router history object.
To do this add in your componentDidMount that has the history prop:
componentDidMount() {
this.props.history.listen(this.onRouteChange.bind(this));
}
onRouteChange(route) {
//route.pathname = Your next route
//Handle what you need to do here
//if you need to redirect you can do this
this.props.history.replace({pathname: '/desiredRoute'});
}

How to Listen for Events in children components in React Native?

I want to implement an event listener from parents to children components in my React Native app, I'm using a StackNavigator as a router.
How can I listen for events occurred in the top/parent components?
Simply use React Native's DeviceEventEmitter.Emit your event from parent component like below:
DeviceEventEmitter.emit('eventKey', {name:'John', age:23});
and listen event in children component
componentDidMount(){
//add listener
this.eventListener = DeviceEventEmitter.addListener('eventKey',this.handleEvent);
}
handleEvent=(event)=>{
//Do something with event object
}
componentWillUnmount(){
//remove listener
this.eventListener.remove();
}
The best approach instead of using an EventEmitter with my current experience (7 months later this question) is using Redux as the state holder, you can create the variables that will pass the message through the components parent-children and then connect the component to get the variables (state) changes immediately it happens.
I decided to use Rematch that is a minified version of Redux and I have got very good results. It forced me to remove all my EventEmitters and use the dispatching methods to update variables (states) that will be updated in the connected components.
Simplest way:
1) Create listener at your app.js (Initial starting file):
Example:
(new NativeEventEmitter()).addListener('loggedIn',() => {
setAuth(true);
});
2) Just emit from any child: (new NativeEventEmitter()).emit('loggedIn');
Hope it will be useful to someone!)
I've been researching in how to emit and listen events from a Parent component to a Child component in React Native, after trying different ways I think that it could be good to share how I have got it working.
In your parent component (App.js in my case), import the EventEmitter lib:
import EventEmitter from 'EventEmitter';
Create a variable instance of the Event Emitter:
export default class App extends Component {
...
componentWillMount() {
this.eventEmitter = new EventEmitter();
}
...
}
Then, when something happens emit the event:
this.eventEmitter.emit('Event-Name', {args});
As I'm using a StackNavigator as a router I have to pass my eventEmitter variable to the children views:
const routes = {
ChildView1: {
screen: ChildComponent1
},
ChildView2: {
screen: ChildComponent2
},
...
};
const AppNavigator = StackNavigator(
{
...routes,
},
{
initialRouteName: 'ChildView1',
headerMode: 'none',
/*
* Use modal on iOS because the card mode comes from the right,
* which conflicts with the drawer example gesture
*/
mode: Platform.OS === 'ios' ? 'modal' : 'card',
}
);
/* Here is the magic for the children views */
render(){
return (
<AppNavigator screenProps={this.eventEmitter} />
);
}
Ok, now we have available our event listener for the children views, let's make a call in one of our children components:
export default class ChildComponent1 extends Component {
ComponentWillMount(){
this.eventEmitter = this.props.screenProps;
this.eventEmitter.addListener('Event-Name', ()=>{
// Do something
});
}
...
}
This is how I have done my event listener for child component, I hope it helps someone else. If you have a better implementation please share/comment it.
Having an Event Emitter can probably get a bit messy when tracking where the event came from or where it's being listened.
Just like you pass the event emitter down to the child via screenProps={this.eventEmitter} (which is what you are essentially doing), why not just pass an actual function your child can use to initiate actions on its parent. That'd probably be the most react way to do things.
It probably not is ideal to add an additional event listener as it will affect performance at some point.
I have followed OPs idea of using redux and it worked well for me. Here is the code...
actionTypes.js:
export const TRIGGER_REQUEST_SUBMIT = "TRIGGER_REQUEST_SUBMIT";
export const ACKNOWLEDGE_REQUEST_SUBMIT = "ACKNOWLEDGE_REQUEST_SUBMIT";
eventActions.js:
import * as types from "./actionTypes";
export function triggerRequestSubmit() {
return function(dispatch) {
dispatch({ type: types.TRIGGER_REQUEST_SUBMIT });
};
}
export function acknowledgeRequestSubmit() {
return function(dispatch) {
dispatch({ type: types.ACKNOWLEDGE_REQUEST_SUBMIT });
};
}
eventReducer.js:
import * as types from "../actions/actionTypes";
import initialState from "./initialState";
export default function eventReducer(state = initialState.event, action) {
switch (action.type) {
case types.TRIGGER_REQUEST_SUBMIT:
return { ...state, shouldSubmitRequest: true };
case types.ACKNOWLEDGE_REQUEST_SUBMIT:
return { ...state, shouldSubmitRequest: false };
default:
return state;
}
}
Don't forget to register reducer in your root-reducer/index.js in reducers
initialState.js:
export default {
event: {
shouldSubmitRequest: false
}
};
Usage in child(Use redux to connect & get shouldSubmitRequest via state, acknowledgeRequestSubmit via dispatch):
useEffect(() => {
if (shouldSubmitRequest) {
console.log("shouldSubmitRequest triggered in Request");
acknowledgeRequestSubmit();
}
}, [shouldSubmitRequest]);
In parent call triggerRequestSubmit(); (connect to redux to get that method)

Resources