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]);
Related
I'm currently trying to convert some hooks over to being inside class components in React. I'm attempting to recreate the code that allows for session storage so that the app doesn't need to fetch from the API so much. Here is the code:
useEffect(() => {
if(sessionStorage.homeState){
// console.log("Getting from session storage");
setState(JSON.parse(sessionStorage.homeState));
setLoading(false);
}else{
// console.log("Getting from API");
fetchMovies(POPULAR_BASE_URL);
}
}, [])
useEffect(() => {
if(!searchTerm){
// console.log("Writing to session storage");
sessionStorage.setItem('homeState', JSON.stringify(state));
}
}, [searchTerm, state])
These are two useEffect hooks so it makes sense to me to go with a regular componentDidMount to get from the session storage. The only thing that I can't seem to figure out is how to recreate the second useEffect that sets session storage and fires only when searchTerm or state changes. searchTerm and state are simply two properties of the state. How could I implement this since componentDidMount only fires once when the app first mounts? Thanks
The only thing that I can't seem to figure out is how to recreate the second useEffect that sets session storage and fires only when searchTerm or state changes. searchTerm and state are simply two properties of the state.
componentDidMount() is only one of methods used by the class components you can recreate the second hook with componentWillUpdate() or shouldComponentUpdate().
For example:
componentWillUpdate(nextProps, nextState) {
if (this.props.searchTerm !== prevProps.searchTerm) {
...
}
}
You can check what lifecycle methods are available in class components by Googling "class component lifecycle".
But as you can read in the comment to your questions Hooks can offer you more than class components, and recreating them is not trivial. It is easier to move from the class component to the Hooks.
Please, look the flux below, it's shows my problem. I'm using vue-router with this.$router.push to browsing on pages. I'm starting on PageA.
PageA -> PageB ( mounted() of PageB is called)
PageB -> PageA (returning to PageA)
PageA -> PageB (mounted() of PageB is not called)
It sounds that page (.vue component) is not closed and mainted on cache or other thing. I must use the mounted() method every time that page is open and maybe close the page and clean from cache. How I can solve it?
vue will re-use components were possible, this is expected.
Normally you would watch for route changes and update your component state accordingly.
To react to route changes you can use beforeRouteUpdate():
const Example = Vue.extend({
template: `
<div>
<p>This changes: '{{param}}'</p>
</div>`,
data(){
return {
param: this.$route.params.param
};
},
beforeRouteUpdate(to, from, next) {
this.param = to.params.param;
next();
}
});
const router = new VueRouter({
routes: [
{
path: '/:param', component: Example,
}
]
})
const app = new Vue({ router }).$mount('#app')
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-link to="/foo">foo</router-link><br>
<router-link to="/bar">bar</router-link><br>
<router-view></router-view>
</div>
Alternatively you can also watch the route and update the state accordingly:
Vue.extend({
watch: {
'$route'() {
// TODO: react to navigation event.
// params cotains the current route parameters
console.log(this.$route.params);
}
},
// ....
});
The vue-router documentation has a few great examples for this: Data Fetching - Vue Router
If you still want to use mounted(), you can do so by giving your router-view a key that will change when the route changes, e.g.:
<router-view :key="$route.fullPath"></router-view>
This will force the component to be re-created every time, so it does have a performance penalty - i would recommend using the route hooks described above, if possible.
Vue 3
For those that ended up here like me looking at why I'm not getting unmounted called on a component inside a page, when route changes, here's some guidance:
Let's say you have Page A that uses Component A in it, and Page B that does not use Component A.
When you open Page A for the first time, Component A will have, as expected, mounted method called.
However, when you navigate to Page B, Vue3 won't un-mount or destroy Component A, rather it will deactivate it, and deactivated method will be called instead of unmounted.
Once you navigate back to Page A, Component A will be reactivated and method activated will be called instead of mounted.
For those who are using the Composition API for Vue 3:
This use case is for fetching data from an API upon component mount and other dependencies. Instead of watch, you need to use watchEffect to automatically track the dependencies and perform side effects on mount.
watchEffect(fetchData);
watch(dependencies, callback) will trigger on first visit to the route. But if you go to another page, then come back to it, it won't trigger again because it does not count the initial state as an update. Also, the dependencies technically did not change from the time it was mounted.
Additional Notes:
If you are coming from React, watch is not exactly equivalent to useEffect. watchEffect is more similar to it.
For comparison, this code should have the same results as the watchEffect one. But watchEffect is more concise.
onMounted(fetchData);
watch([dependency 1, dependency 2, ...], fetchData);
I have a route which takes an id and renders the same component for every id, for example :
<Route path='/:code' component={Card}/>
Now the in the Link tag I pass in an id to the component.Now the Card component fetches additional detail based on the id passed. But the problem is it renders only for one id and is not updating if I click back and goto the next id. I searched and found out that componentsWillReceiveProps can be used but during recent versions of React it has been deprecated. So how to do this?
Putting current location as key on component solves problem.
<Route path='/:code' component={(props) => <Card {...props} key={window.location.pathname}/>}/>
I just ran into a similar problem. I think you are conflating updating/rerendering and remounting. This diagram on the react lifecycle methods helped me when I was dealing with it.
If your problem is like mine you have a component like
class Card extend Component {
componentDidMount() {
// call fetch function which probably updates your redux store
}
render () {
return // JSX or child component with {...this.props} used,
// some of which are taken from the store through mapStateToProps
}
}
The first time you hit a url that mounts this component everything works right and then, when you visit another route that uses the same component, nothing changes. That's because the component isn't being remounted, it's just being updated because some props changed, at least this.props.match.params is changing.
But componentDidMount() is not called when the component updates (see link above). So you will not fetch the new data and update your redux store. You should add a componentDidUpdate() function. That way you can call your fetching functions again when the props change, not just when the component is originally mounted.
componentDidUpdate(prevProps) {
if (this.match.params.id !== prevProps.match.params.id) {
// call the fetch function again
}
}
Check the react documentation out for more details.
I actually figured out another way to do this.
We'll start with your example code: <Route path='/:code' component={Card}/>
What you want to do is have <Card> be a wrapper component, functional preferrably (it won't actually need any state I don't think) and render the component that you want to have rendered by passing down your props with {...props}, so that it gets the Router properties, but importantly give it a key prop that will force it to re-render from scratch
So for example, I have something that looks like this:
<Route exact={false} path="/:customerid/:courierid/:serviceid" component={Prices} />
And I wanted my component to rerender when the URL changes, but ONLY when customerid or serviceid change. So I made Prices into a functional component like this:
function Prices (props) {
const matchParams = props.match.params;
const k = `${matchParams.customerid}-${matchParams.serviceid}`;
console.log('render key (functional):');
console.log(k);
return (
<RealPrices {...props} key={k} />
)
}
Notice that my key only takes customerid and serviceid into account - it will rerender when those two change, but it won't re-render when courierid changes (just add that into the key if you want it to). And my RealPrices component gets the benefit of still having all the route props passed down, like history, location, match etc.
If you are looking for a solution using hooks.
If you are fetching data from some API then you can wrap that call inside a useEffect block and pass history.location.pathname as a parameter to useEffect.
Code:
import { useHistory } from "react-router";
const App = () => {
const history = useHistory();
useEffect(() => {
//your api call here
}, [history.location.pathname]);
};
useHistory hook from react-router will give the path name so the useEffect will be called everytime it (url) is changed
as described by #theshubhagrwl but
you can use location.href instead of location.pathname to work in all condition
import { useHistory } from "react-router";
const App = () => {
const history = useHistory();
useEffect(() => {
// do you task here
}, [history.location.href]);
};
You can use use UseLocation() from "react-router-dom"
and then use that object in useEffect dependency array.
import {useLocation} from "react-router-dom";
export default function Card() {
const location = useLocation();
useEffect(()=>{}, [location]);
return(
// your code here
);
}
In React Router v4 Adding a Switch tag after Router fixes the problem
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)
I am currently working on a simple React app with a very common workflow where users trigger Redux actions that, in turn, request data from an API. But since I would like to make the results of these actions persistent in the URL, I have opted for React Router v4 to help me with the job.
I have gone through the Redux integration notes in the React Router documentation but the idea of passing the history object to Redux actions just doesn't feel like the most elegant pattern to me. Since both Redux and Router state changes cause React components to be re-rendered, I'm a little worried the component updates could go a bit out of control in this scenario.
So in order to make the re-rendering a bit more predictable and sequential, I have come up with the following pattern that attempts to follow the single direction data flow principle:
Where I used to trigger Redux actions as a result of users' interactions with the UI, I am now calling React Router's props.history.push to update the URL instead. The actual change is about updating a URL parameter rather than the whole route but that's probably not that relevant here.
Before:
// UserSelector.jsx
handleUserChange = ({ target: selectElement }) => {
// Some preliminary checks here...
const userId = selectElement.value
// Fire a Redux action
this.props.setUser(userId)
}
After:
// UserSelector.jsx
handleUserChange = ({ target: selectElement }) => {
// Some preliminary checks here...
const userId = selectElement.value
// Use React Router to update the URL
this.props.history.push(`/user-selector/${userId}`)
}
The userId change in the URL causes React Router to trigger a re-render of the current route.
Route definition in App.jsx:
<Route path="/user-selector/:userId?" component={UserSelector} />
During that re-render, a componentDidUpdate lifecycle hook gets invoked. In there I am comparing the previous and current values of the URL parameter via the React Router's props.match.params object. If a change is detected, a Redux action gets fired to fetch new data.
Modified UserSelector.jsx:
componentDidUpdate (prevProps) {
const { match: { params: { userId: prevUserId } } } = prevProps
const { match: { params: { userId } } } = this.props
if (prevUserId === userId) {
return
}
// Fire a Redux action (previously this sat in the onChange handler)
this.props.setUser(userId)
}
When the results are ready, all React components subscribed to Redux get re-rendered.
And this is my attempt to visualise how the code's been structured:
If anyone could verify if this pattern is acceptable, I would be really grateful.
For step 3, I suggest a different approach which should be more in line with react-router:
react-router renders a component based on a route
this component should act as the handler based on the particular route it matches (think of this as a container or page component)
when this component is mounted, you can use componentWillMount to fetch (or isomorphic-fetch) to load up the data for itself/children
this way, you do not need to use componentDidUpdate to check the URL/params
Don't forget to use componentWillUnmount to cancel the fetch request so that it doesn't cause an action to trigger in your redux state
Don't use the App level itself to do the data fetching, it needs to be done at the page/container level
From the updated code provided in the question:
I suggest moving the logic out, as you would most likely need the same logic for componentDidMount (such as the case when you first hit that route, componentDidUpdate will only trigger on subsequent changes, not the first render)
I think it's worth considering whether you need to store information about which user is selected in your Redux store and as part of URL - do you gain anything by structuring the application like this? If you do, is it worth the added complexity?