React-Native navigation jumps to wrong screen with useEffect and setTimeout - react-native

I have a navigation stack with different screens that I wan't to render after each other. I set the timeout to be 5 seconds, so it will go to the next screen in the stack.
<Stack.Screen name="StoryScreenOne" component={StoryScreenOne} />
<Stack.Screen name="StoryScreenTwo" component={StoryScreenTwo} />
<Stack.Screen name="StoryScreenThree" component={StoryScreenThree} />
My timeout function is this
useEffect(() => {
setTimeout(() => {
props.navigation.navigate('StoryScreenTwo');
}, 5000);
});
The problem is that if I am at screen two and navigate back to screen one, the next screen that renders is screen three, not screen two as I want it to be.
Any tips for this?

If i understand when you arrive on the screen two there's an other useEffect like this:
useEffect(() => {
setTimeout(() => {
props.navigation.navigate('StoryScreenThree');
}, 5000);
});
I think the setTimeout is still running so if you want to rerender screen 2 on goBack you need to use a clearTimeOut when you press the back button.
Look a this : https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals
But your first screen will not unmount when you go navigate so you need to use a listener to relaunch the UseEffectlike this :
import { useFocusEffect } from '#react-navigation/native';
function Profile() {
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
};
}, [])
);
return <ProfileContent />;
}
more info here : https://reactnavigation.org/docs/navigation-lifecycle/

Related

React native disable timer in bottom navigation not working

I have 4 screens in bottom navigation.
The first screen consists of a map with the timer of 10s . As soon as the timer get executed the api is hit.
But when a user switch the screen with bottom navigation tab item . The timer still works in background and due to which the other api start having lag.
How to make sure the timer only works when that screen is focused?
I tried updating the name of screen user is navigating using useContext however when the timer execute it do not return the update name of the screen. every time it returns the older screen name.
This code is in all the 4 bottom navigation screens. As I have observed useEffect only works once. and whenever user clicks it second time this hook do not get trigger.
HOME SCREEN
const {activeScreenFun, activeScreen, previousScreen} = useNavigationCustom();
React.useEffect(() => {
const unsubscribe = navigation.addListener('tabPress', e => {
activeScreenFun('Home');
});
return unsubscribe;
}, [navigation]);
useEffect(() => {
activeScreenFun('Home');
}, []);
Timer
useEffect(() => {
if (timer) {
let interval = setInterval(() => {
getAPiData();
}, 10000);
return () => {
clearInterval(interval);
};
}
}, [timer]);
NavigationCustomProvider Context
export function NavigationCustomProvider({children}) {
const [activeScreen, setActiveScreen] = useState('');
const [previousScreen, setPreviousScreen] = useState('');
const activeScreenFun = useCallback(async function (activeScreenSelected) {
setPreviousScreen(activeScreen);
setActiveScreen(activeScreenSelected);
});
const getActiveScreenFun = useCallback(() => {
return activeScreen;
});
Bottom Navigation Code
export default function MainScreen() {
return (
<NavigationCustomProvider>
<MainLayout>
<MainLayoutScreen
name={HOME_ROUTE}
icon={TrackItIcon}
activeIcon={TrackItActiveIcon}
component={HomeScreen}
/>
<MainLayoutScreen
name={ATTENDACE_ROUTE}
icon={AttendanceIcon}
activeIcon={AttendanceActiveIcon}
component={AttendanceScreen}
/>
<MainLayoutScreen
name={NOTIFICATION_ROUTE}
icon={NotificationIcon}
activeIcon={NotificationActiveIcon}
component={NotificationScreen}
/>
<MainLayoutScreen
name={MY_ACCOUNT_ROUTE}
icon={AccountIcon}
activeIcon={AccountActiveIcon}
component={ProfileScreen}
/>
</MainLayout>
</NavigationCustomProvider>
);
}
TAB BAR CODE
routes = children.map(x => ({
name: x.props.name,
icon: x.props.icon,
activeIcon: x.props.activeIcon,
component: x.props.component,
}));
<Tab.Navigator
barStyle={{backgroundColor: theme.colors.white}}
activeColor={theme.colors.primary}
shifting={true}
labeled={true}>
{routes.map(x => {
let Icon = x.icon;
let ActiveIcon = x.activeIcon;
return (
<Tab.Screen
key={x.name}
name={x.name}
component={x.component}
options={{
tabBrColor: theme.colors.white,
tabBarIcon: ({focused}) =>
focused ? <ActiveIcon /> : <Icon />,
}}
/>
);
})}
</Tab.Navigator>
A new timer instance is created for each new component rerender.
Even if you clear an instance of the timer when the component unmounts, previously created instances are still running in the background.
you need to persist a single instance of timer across all component rerender cycles.
React provide hook useRef to persist value for all component render cycle.
let interval = React.useRef(null);
useEffect(() => {
if (timer) {
// Assign and persist Timer value with ref
interval.current = setInterval(() => {
getAPiData();
}, 10000);
return () => {
if (interval.current) {
clearInterval(interval);
}
};
}
}, [timer]);

How do I update react-native component, when click on tab on bottom Tab Navigator

I am using bottom tab navigator in React-native for Navigation. When I switches tab, component are not updating.
Pls let me know how can I update/refresh whole component when I tap on tab at bottom Tab Navigator
Here is a simple solution.
import { useFocusEffect } from '#react-navigation/native';
useFocusEffect(
React.useCallback(() => {
console.log("Function Call on TAb change")
}, [])
);
Here is the link you can read more. https://reactnavigation.org/docs/function-after-focusing-screen/
You can use Navigation listener check Navigation Events, when screen gets focused it will trigger a function like this:
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
//Your refresh code gets here
});
return () => {
unsubscribe();
};
}, [navigation]);
And class component like this:
componentDidMount() {
this._unsubscribe = navigation.addListener('focus', () => {
//Your refresh code gets here
});
}
componentWillUnmount() {
this._unsubscribe();
}
If you want to force update check this question

Tab bar navigation without component screen render

Is there a way to press on tab bar and not render component screen? i have been passing null function but it still renders blank screen. i want that wherever you press it stays on home screen.
Add a listener to the Screen.
component={() => null}
listeners={() => ({
tabPress: (e) => {
e.preventDefault(); // Prevents navigation
// Your code here for when you press the tab
},
})}
You can avoid navigation to the screen by creating your custom tabBar component in which you can handle onPress to each tabBarComponent so you can avoid navigation and do some action instead. Take a closer look at the example from react-navigation documentation here:
https://reactnavigation.org/docs/bottom-tab-navigator/#tabbar
This line of code from the example should be helpful for you:
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
navigation.navigate(route.name);
}
};

How do we implement Scroll to top functionality on tapping of the corresponding tab button on a bottom tab navigator in react navigation?

The React Navigation version I am using is v5. In the ScrollView corresponding to a Bottom Tab Icon, if the user is already on that given screen, I want to enable functionality where the user scrolls to the top when this icon is pressed.
As stated in the documentation, this feature should be already implemented. But I think you have placed your ScrollView inside a nested StackNavigator, right?
In that case, you probably need to subscribe to TabNavigator event and fire your scrollToTop manually
React.useEffect(() => {
const unsubscribe = navigation.addListener('tabPress', e => {
// Get your scrollView ref and dispatch scrollToTop
});
return unsubscribe;
}, [navigation]);
Hope it'll help you!
None of the solutions worked for me on the web, not using listener and not using useScrollToTop which is provided by react-navigation. Because I have nested stacks and navigators and also custom tabbar.
I solved the problem by setting a navigation param for scrolling to the top.
I have custom tabbar, and I needed to scroll to top on the homepage which was the first route of the tab stack.
So, in the homepage I set the scrollToTop parameter for the navigator:
const homelistRef = React.useRef<FlatList | null>(null)
useFocusEffect(
useCallback(() => {
navigation.setParams({
scrollToTop: () => homelistRef.current?.scrollToOffset({ offset: 0, animated: true }),
});
}, [navigation, homelistRef]),
);
return (
<FlatList
ref={homelistRef}
...{other Flatlist configs}
/>
)
And in my Tabbar component, I read this new param and execute it in the onPress function of the tabs:
interface IProps extends BottomTabBarProps {}
const TabBar: React.FC<IProps> = ({ state, navigation }) => {
const handleTabPress = useCallback(
(route) => async () => {
const currentRoute = state.routes?.[state.index];
if (
route === currentRoute?.name &&
state.routes?.[0].state?.routes?.[0]?.params?.scrollToTop
) {
state.routes[0].state.routes[0].params.scrollToTop();
}
},
[navigation, state],
);
return (
{render tab component}
)
}
And that's the ultimate solution for executing some action on active tabs.

Set navigation parameter on goBack in react native

I have two screens A and B.
When i navigate from Screen A to Screen B I have passed Parameter "onGoBack":false.
On B Screen I have setParameter "onGoBack" as true Which is not working. On go back from Screen B I always receiving "onGoBack" parameter as false only.
Please help to solved this problem.
How can I received updated parameter value on go back.
Below the code I have used to set parameter on Go back from screen B
componentDidMount() {
this.props.navigation.setParams({onGoBack: true});
console.log('Updated Go Back Value: '+this.props.navigation.state.params.onGoBack);
this.backHandler = BackHandler.addEventListener("hardwareBackPress", () => {
this.props.navigation.goBack();
return true;
});
}
You can get the navigation state params whenever a screen is mounted so try this:
import { NavigationEvents } from 'react-navigation';
render() {
return (
<View style={{ flex: 1 }}>
<NavigationEvents
onWillFocus={() => {
// Do your things here
}}
/>
</View>
);
}
Or else,
You can send the params when you're navigating from screen A to screen B:
this.props.navigation.navigate('screenB', { onGoBack: false });
So screen A is already mounted on Stack, so you can use the navigate function to send params. Here, the navigate function will act as goBack(); it won't push a new screen in the stack
this.props.navigation.navigate('screenA', { onGoBack: true });