EXPO useEffect not called on navigating to same screen - javascript

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.

Related

How do I use React Navigation to send parameters to another screen without moving there?

I know that you can do navigation.navigate("address", {/* params go here */ to send parameters over to another screen. But then you have to navigate there. Is there a way of sending params over without navigating?
I have a application with multiple screens. And I want to update a useState from another component by updating its params so that a button appears. But I dont want to navigate there, I just want to update it so when the user does go there the button will be there.
Like this:
const currentComponent = (navigation) {
return (
<Button onPress={navigation.updateParams("otherComponent", {shouldShowValue: true})} />
)
}
const otherComponent = (route, navigation) {
const {shouldShowValue} = route.params
const [shouldShow, setShouldShow] = useState(shouldShowValue);
return (
{shouldShow ? <Button> Yayy this button appears now <Button /> : null}
)
}
}
'''
this is just pseudo code and not at all
like the code I have written,
but its just meant as an example to get a
understanding of what I mean.
(updateParams) isnt a function that exists,
but I want something similiar like it.
Is there a way of updating the params in a
component from another component without having
to navigate there? Like with
navigate.navigate("address" {params go here})
but without the navigation part?
You can consider using useContext() hook to execute your functionality.
Using navigation library to pass param without navigating to that page is somehow misusing the navigation function.
With useContext, you can share the state(s) among components. If you want to change the value upon clicking action, you can also pass the useState hook into useContext. Alternatively, you can consider to use redux library to share state.
import { useState, createContext, useContext } from 'react';
const shareContext = createContext(null);
export default function demoUseContext() {
const [isClicked, setClicked] = useState(false);
return (
<shareContext.Provider value={{isClicked, setClicked}}>
<ComponentA />
<ComponentB />
</shareContext.Provider>
)
}
function ComponentA() {
const sharedParam = useContext(shareContext);
return (
<button onClick={() => sharedParam.setClicked(!sharedParam.isClicked)}>
click to change value
</button>
);
}
function ComponentB() {
const sharedParam = useContext(shareContext);
return (
sharedParam.isClicked && <div>it is clicked</div>
)
}
As the example above, the code pass the useState hook from parent component into context, where A is consuming the useState from context to setup isClicked via setClicked, B is consuming the value isClicked from context.
You can also manage to setup context with value not only in a hook, but a param / object / function as a callback.
For more details, please refer to https://reactjs.org/docs/hooks-reference.html#usecontext
There're multiple hooks including useContext fyi
Passing parameters to routes
There are two pieces to this:
Pass params to a route by putting them in an object as a second parameter to the navigation.navigate function: navigation.navigate('RouteName', { /* params go here */ })
Read the params in your screen component: route.params.
We recommend that the params you pass are JSON-serializable. That way, you'll be able to use state persistence and your screen components will have the right contract for implementing deep linking.

component is not unmounting react-native

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

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)
}
}, []);

Run function from a different class in react native

I have a very simple issue with my react native application, I just want to execute a function everytime a button is clicked, it become complicated when there's separate classes and components.
I have 2 screens Dashboard and Search and 2 components Navbar and Results
In Dashboard I grab some user input and store it in selectedIngredients variable
and with the Navbar component I execute a function located in the same file.
<Navbar handle={() => this.switcher()} />
This function is where should the magic happens (or maybe in Search.js screen?)
switcher() {
const { navigate } = this.props.navigation;
navigate('Select', {passedData:this.state.selectedIngredients });
Alert.alert("send data to Search.js")
}
Select is Search.js screen
Everything workin fine and I move to the expected screen with the expected user input selectedIngredients, this is the first render of Search.js screen.
componentDidMount() {
this.apiCall();
Alert.alert("did mount search.js")
}
After that I'm stuck because everytime I click on the btn on my Navbar and execute the switcher() function, componentDidMount do not run anymore so I have to refresh the page by clicking on another button, this is exactly what I'm trying to avoid because it's bad for UX, like really bad. I am not looking to update Results component automatically but just update it with one function.
The code below is not that important it only shows apiCall function and render of Results component, I don't know if I should put more information. Please someone help
apiCall() {
fetch(url)
.then((response) => response.json())
.then((responseJson) => {
this.setState({
data: responseJson.results,
});
})
}
componentDidMount() {
this.apiCall();
Alert.alert("did mount search.js")
}
render() {
return (
<View>
<Navbar title="Results" />
<Results results={this.state.data} />
</View>
);
}
}
My attempt was to add this.props.apiCall() in switcher function but got undefined error, something like hey react native! please send this data to Search.js screen and when you arrive please execute apiCall function, it's located there, not here.
Since you are using react navigation, in Search.js you have to bind didFocus listener, so the api will be called every time your screen is focused
componentDidMount() {
const { navigation } = this.props;
navigation.addListener( 'didFocus', () => this.apiCall() );
this.apiCall();
Alert.alert("did mount search.js")
}

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