component is not unmounting react-native - javascript

In my code I have a problem with unmount a component. I am using drawer-navigator and when i navigating between drawer-screens the previous screen is not dying and when i open that screen again everything is still there.
But i want to re-render or unmount and mount the component again when i navigate between drawer-screens. Is there a way for that? Or how can i make my component unmount manually? I know there is a lot of question about this but i couldn't find that i want.
My react native version is 0.63

react navigation never destroy the screen. Its mean your screen will never unMount or when you go back it will never remount (didMount). To Perform such see the example below
import React, { useCallback } from 'react';
import { useFocusEffect } from '#react-navigation/native';
const Home = () => {
useFocusEffect(
useCallback(() => {
// Do something when the screen is focused
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
};
}, [])
);
return <Home />;
}
Reference : Navigation Lifecycle

You should check this page of react-navigation documentation
Consider a stack navigator with screens A and B. After navigating to
A, its componentDidMount is called. When pushing B, its
componentDidMount is also called, but A remains mounted on the stack
and its componentWillUnmount is therefore not called.
Conclusion, the screens are not detroyed and re-created everytime you navigate, it's by design.
If you want to execute some code when you leave or open again a page you should use the lifecycle events presented in the same documentation

Related

React JS: Why is my cleanup function triggering on render? [duplicate]

I have a counter and a console.log() in an useEffect to log every change in my state, but the useEffect is getting called two times on mount. I am using React 18. Here is a CodeSandbox of my project and the code below:
import { useState, useEffect } from "react";
const Counter = () => {
const [count, setCount] = useState(5);
useEffect(() => {
console.log("rendered", count);
}, [count]);
return (
<div>
<h1> Counter </h1>
<div> {count} </div>
<button onClick={() => setCount(count + 1)}> click to increase </button>
</div>
);
};
export default Counter;
useEffect being called twice on mount is normal since React 18 when you are in development with StrictMode. Here is an overview of what they say in the documentation:
In the future, we’d like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React will support remounting trees using the same component state used before unmounting.
This feature will give React better performance out-of-the-box, but requires components to be resilient to effects being mounted and destroyed multiple times. Most effects will work without any changes, but some effects do not properly clean up subscriptions in the destroy callback, or implicitly assume they are only mounted or destroyed once.
To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount.
This only applies to development mode, production behavior is unchanged.
It seems weird, but in the end, it's so we write better React code, bug-free, aligned with current guidelines, and compatible with future versions, by caching HTTP requests, and using the cleanup function whenever having two calls is an issue. Here is an example:
/* Having a setInterval inside an useEffect: */
import { useEffect, useState } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => setCount((count) => count + 1), 1000);
/*
Make sure I clear the interval when the component is unmounted,
otherwise, I get weird behavior with StrictMode,
helps prevent memory leak issues.
*/
return () => clearInterval(id);
}, []);
return <div>{count}</div>;
};
export default Counter;
In this very detailed article called Synchronizing with Effects, React team explains useEffect as never before and says about an example:
This illustrates that if remounting breaks the logic of your application, this usually uncovers existing bugs. From the user’s perspective, visiting a page shouldn’t be different from visiting it, clicking a link, and then pressing Back. React verifies that your components don’t break this principle by remounting them once in development.
For your specific use case, you can leave it as it's without any concern. And you shouldn't try to use those technics with useRef and if statements in useEffect to make it fire once, or remove StrictMode, because as you can read on the documentation:
React intentionally remounts your components in development to help you find bugs. The right question isn’t “how to run an Effect once”, but “how to fix my Effect so that it works after remounting”.
Usually, the answer is to implement the cleanup function. The cleanup function should stop or undo whatever the Effect was doing. The rule of thumb is that the user shouldn’t be able to distinguish between the Effect running once (as in production) and a setup → cleanup → setup sequence (as you’d see in development).
/* As a second example, an API call inside an useEffect with fetch: */
useEffect(() => {
const abortController = new AbortController();
const fetchUser = async () => {
try {
const res = await fetch("/api/user/", {
signal: abortController.signal,
});
const data = await res.json();
} catch (error) {
if (error.name !== "AbortError") {
/* Logic for non-aborted error handling goes here. */
}
}
};
fetchUser();
/*
Abort the request as it isn't needed anymore, the component being
unmounted. It helps avoid, among other things, the well-known "can't
perform a React state update on an unmounted component" warning.
*/
return () => abortController.abort();
}, []);
You can’t “undo” a network request that already happened, but your cleanup function should ensure that the fetch that’s not relevant anymore does not keep affecting your application.
In development, you will see two fetches in the Network tab. There is nothing wrong with that. With the approach above, the first Effect will immediately get cleaned... So even though there is an extra request, it won’t affect the state thanks to the abort.
In production, there will only be one request. If the second request in development is bothering you, the best approach is to use a solution that deduplicates requests and caches their responses between components:
function TodoList() {
const todos = useSomeDataFetchingLibraryWithCache(`/api/user/${userId}/todos`);
// ...
Update: Looking back at this post, slightly wiser, please do not do this.
Use a ref or make a custom hook without one.
import type { DependencyList, EffectCallback } from 'react';
import { useEffect } from 'react';
const useClassicEffect = import.meta.env.PROD
? useEffect
: (effect: EffectCallback, deps?: DependencyList) => {
useEffect(() => {
let subscribed = true;
let unsub: void | (() => void);
queueMicrotask(() => {
if (subscribed) {
unsub = effect();
}
});
return () => {
subscribed = false;
unsub?.();
};
}, deps);
};
export default useClassicEffect;

EXPO useEffect not called on navigating to same screen

I have one screen which called useEffect when rendered first time in the flow.
The second time I navigate to the screen in the flow , use Effect is not called but I want to call a function upon first time before , after or during render function is called when we navigate a second time to the same screen.
Here is the navigate call for everytime navigating to this screen from various screens
navigation.navigate("x", { u:type, s:title});
The following is the structure for my screen. I am not using components but functions with navigation
const x = ({ navigation}) => {
...
return (
<View style={styles.a}>
...
</View>
);
};
export default x;
The problem here is that a screen remains mounted in react-native after the initial navigation. Thus, a useEffect with an empty dependency array won't be called subsequently on navigation.
Notice that this behavior differs from the web.
If you are coming to react-navigation from a web background, you may assume that when user navigates from route A to route B, A will unmount (its componentWillUnmount is called) and A will mount again when user comes back to it. While these React lifecycle methods are still valid and are used in react-navigation, their usage differs from the web.
The recommended way to solve this is described here. For your case this can be solved as follows.
import { useFocusEffect } from '#react-navigation/native';
const x = ({ navigation}) => {
useFocusEffect(
React.useCallback(() => {
// called when screen is focused, thus everytime on navigation
return () => {
// unfocus... cleanup ... or whatever
};
}, [])
);
...
return (
<View style={styles.a}>
...
</View>
);
};
export default x;
useEffect will be called on first load of your functional component or when some dependency will be changed. Everything remains the same, so your useEffect will not work. Consider using useFocusEffect from react-navigation lib instead.

Why am I getting the following React Native warning: Can't perform a React state update on an unmounted component

I'm trying to build a React Native app, but am still kind of new to the React/RN ecosystem, so I may just be misunderstanding something obvious with the problem I'm having.
I have an app where a lot of the pages are structured as follows:
<View>
<NavComponent />
<View>
{/* Page component structure/logic here */}
</View>
</View>
NavComponent loads a toggleable nav menu with TouchableOpacity elements like the following:
Go to Screen #1
Go to Screen #2
Go to Screen #3
The problem I'm having (and maybe this isn't a problem so much as just how React/RN works) is that if I start on screen #1, open the nav, go to screen #2, open the nav again, and then go back to screen #1, even though screen #1 shows up again, the actual rendering function for screen #1 doesn't seem to be called again, and since NavComponent is part of the rendering of each screen, when I try to open the nav from screen #1 again, I get the following warning:
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in %s.%s, a
useEffect cleanup function ...
Again, maybe my approach with the app is flawed to begin with, but essentially, when I go from one screen to another from the nav, I always want the new screen to re-render from scratch (including the initial Ajax call for data).
Here's a more fleshed-out example of the render function for a screen (they all follow this same basic pattern):
const screen1 = ({ navigation }) => {
const [serverData, setServerData] = useState(null);
useEffect(() => {
// getPageData is a custom method I added to axios.
axios.getPageData('/api/url/here', (data) => {
setServerData(data);
});
}, []);
if (serverData) {
const { meta, user, data } = serverData;
return (
<View>
<NavComponent />
<View style={styles.container}>
{/* Page component structure/logic here */}
</View>
</View>
);
}
return null;
};
If, for example, I added a console.log to the beginning of the render function above, it's called the first time the screen is loaded, but if I go to screen #2 and then come back to screen #1 via the nav component, the console.log isn't output again. Why?
And for what it's worth, I'm using the standard navigation.navigate('ScreenName') in NavComponent to go from screen to screen.
Any advice on how to fix the warning (and/or just better design the app) so that I can have that nav on every page would be greatly appreciated. Thank you.
your api call is resulting in the warning, in the react/native ecosystem, when a component is removed from the tree, the developer needs to cancel all subscriptions (listeners to events) and async tasks(fetching data from the web), those function need to be canceld by the developer as react/native cant do that for you.
to handle that in a class based component, you need to impelment componentWillUnmount and remove the subscriptions there
class MyClass extends Component {
componentWillUnmount() {
// remove listeners and cancel requests
}
but in a modern hook component , you need to return a cleanup function, a function to return in useEffect that will be called by react to cancel any subscriptions you have made, in your case, just return a cleanup function should remove that warning for you
const [mounted, setIsMounted] useState(false)
useEffect(() => {
// getPageData is a custom method I added to axios.
setIsMounted(true)
axios.getPageData('/api/url/here', (data) => {
if(isMounted)
setServerData(data);
});
return () => {
setIsMounted(false)
}
}, []);

Cannot render same route with different parameters react router v4

I am implementing a react page with route info/:id. From this page, there are various URLs which are navigated by NavLink.
For example: if current route is localhost:3000/info/1 and there are links localhost:3000/info/2 and localhost:3000/info/3, clicking on those links will load the page I'm currently on i.e: localhost:3000/info/1. While tracking for props changes, url is getting changed briefly but it's again loading the same page. What shall I do to route to same component with different parameters?
Thanks in advance!!!!!!
You should track your page updates in componentDidUpdate lifecycle method or useEffect hook.
Class components:
componentDidUpdate(prevProps) {
if(prevProps.match.params.id !== this.props.match.params.id){
// do something
}
}
Functional components:
useEffect(() => {
// do something
}, [props.match.params.id]);
Here's how you achieve same via React hooks and functional components:
useEffect(() => {
//do something
}, [props.match.params.id]);

Component re-renders when using react-navigation for react native

I noticed that whenever I navigate to another page using the navigate props available to my component, it triggers a re-render of the component and componentDidMount is being called whenever I navigate to a screen that has rendered before.
For instance, when I navigate a user to their profile page and they decided to go back to the dashboard, the dashboard component which has been initially rendered is being rendered again and componentDidMount is being called thereby slowing down the application.
import { StackNavigator } from 'react-navigation';
const Routes = StackNavigator({
home: {
screen: HomeScreen
},
dashboard: {
screen: Dashboard
},
profile: {
screen: Profile
}
},
{
headerMode: 'none',
});
In my component I navigate the user with this.props.navigation.navigate('ScreenName')
I would appreciate any help to stop the component from re-rendering when navigating back to it. Thanks
I would have a state variable in your constructor that keeps track if you navigated. State is only relevant to the current component. So if you navigate to 'ScreenName' multiple times, the stack builds and each ScreenName component has its own state.
constructor(props)
super(props)
this.state = {
navigatedAway : false
}
Then before you navigate to your 'ScreenName' screen update the state
this.setState({
navigatedAway : true
},
() => {
this.props.navigation.navigate('ScreenName');
}
);
Use syntax above to make sure state isUpdated THEN navigate. Then like Dan said in comments above if your function shouldComponentUdate have a condition statement.
shouldComponentUpdate(newProps){
// return true if you want to update
// return false if you do not
}
* Side Note *
When you navigate I don't believe the component is unmounted. You could verify this by simply printing to console. Correct me if I am wrong though, I am fairly new to react native.
componentDidMount() {
console.log("COMPONENT_CONTENT_MOUNTED")
}
componentWillUnmount({
console.log("COMPONENT_CONTENT_UNMOUNTED")
}
If you are using React Navigation 5.X, just do the following:
import { useIsFocused } from '#react-navigation/native'
export default function App(){
const isFocused = useIsFocused()
useEffect(() => {
//Update the state you want to be updated
} , [isFocused])
}
If I understand your question correctly, when you navigate away, the component is unmounted.
When you navigate back, it must be re-mounted, hence re-rendered.
In general, any UI change necessitates a re-render. No way around that - It's kind of "by definition".
You might be able to cache the page.
Or use the reselect library to cache expensive to obtain data, so the calculations for re-rendering are quick and minimal.
If react/react-native thinks the props have changed (in an already mounted/rendered component), it will also re-render, but you can influence this decision via shouldComponentUpdate().
Just add React.memo to your export of component that reload each time.
So instead of
export default component
you would have:
export default React.memo(component);
Which does a comparison of props and only re-renders if props change (so not on navigate but on actual changes, which is what you want)

Categories