How to refresh a component after already visiting it? - reactjs

Working on a project and when changing routes, coming back to one after already visiting it doesnt display new changes/additions. For example, if i route to my "Offers" route, it will display my offers on current items for rent. If i then leave that route and go and place another offer on an item, routing back to "Offers" will reveal no changes unless I refresh the page. I populate the state in componentdidmount() but have also tried other ways where im not using componentdidmount() to populate it. What options do I have here? I can post the component if needed.

Going back to a route which was already visited in the same BrowserRouter context, will not MOUNT the component again. It would only cause the router to perform a pop action, which causes the browser to pop the latest DOM generated for that route to pop from the router context. So what you would have to do, is to check if the user has pressed back button in his/her browser. To do so, you can check this.props.history.action === 'POP' in componentWillReceiveProps lifecycle. Something like this:
class Offers extends Component {
constuctor (props) {
super(props);
this.state = {
offers: []
}
}
componentDidMount () {
this.fetchOffersAndStoreThemInState();
}
componentWillReceiveProps () {
if (this.props.history.action === 'POP') {
this.fetchOffersAndStoreThemInState();
}
}
}
And don't forget to wrap your component with withRouter to have access to the history object.

Related

React Router v4 replace history when component is unmounted

I have a app which has the Following Components
|__Base - /home/Base
|__Try - /home/Base/:try
|__Report - /home/Base/:try/report
Base is the Starting screen where the user hits a button and clicks on Try and after trying some things he hits submits which generates reports which has some back end interactions and when the data is fetched it loads the Reports.
So what i want is when the user hits the back button from the Reports Page he should not land on the Try page but on the Base page .
For that to work i went through the react router documentation and was trying to use history.replace on componentWillUnmount for Reports Page
this.props.history.replace(`/home/Base`, {
pathname: `/home/Base`,
search: null,
state: {
isActive: true
}
}, null);
In case the Report Page is FullyLoaded and i press the back button it works but calls the Try Render Method too and then takes me to the Base Page , But in case of Reports Not fully Loaded and i press the back button while the loading spinner is in progress it goes to base page still but also mounts and unmounts the TRY component.
What am i missing here , what causes it to mount/unmount or render the previous component and then load the base component even though i replace the history stack ?
Reason
Related with this issue
React v16, changing routes, componentWillMount of the new route is called before componentWillUnmount of the old route
Update:
Solution (checked, update online demo later)
Use react-router-last-location to get previous pathname
import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom';
import { LastLocationProvider } from 'react-router-last-location';
<BrowserRouter>
<LastLocationProvider>
<Switch>
...
</Switch>
</LastLocationProvider>
</BrowserRouter>
Check previous pathname in componentWillMount, if it's from certain page, push a new pathname to route.
componentWillMount() {
const { history, lastLocation } = this.props;
if (lastLocation?.pathname === '/home/Base/:try/report') {
history.push({pathname: '/home/Base'});
}
}
You can use the HOC they provide or write it yourself refer to the lib's source to reduce the dependencies
import { withLastLocation } from 'react-router-last-location';
interface Props {
lastLocation: any,
history: any,
}
export const YourComponent = withLastLocation(connect(
...
))
In this way you can redirect all the routing process from certain pages without mount current page, no matter you clicked a back button or clicked the back in your browser.

Where to Put Code that should run First ReactJs + Mobx State Tree

I have some code that grabs the users ipAddres. I do this right now in my componentDidMount in my app.js
async componentDidMount() {
await eventTrackingStore.getIpAddress();
}
So I did it in my app.js as it is my root component and I only want to set this once. This works fine if the user starts from the home page and navigates through the site.
However some pages can be loaded directly(ie you type in the url in your browser and it goes straight to that page).
Since the react lifecycle starts with most immediate component, which calls a method that expects the ipAddress code to be set but it does not get set till it hits the app.js
Now I could put the above code in each method but that gets tedious. Is there some sort of method in reactjs, or mbox or mbox state tree that would fire first?
If you use mobx-state-tree and you have a global store, then that global store can make the API call in the afterCreate method
const AppModel = types.model({
ips: types.array(types.string)
}).actions(self => ({
afterCreate() {
flow(function*() {
const ips = yield eventTrackingStore.getIpAddress();
self.setIps(ips)
})()
},
setIps(ips: string[]) {
self.ips = ips
}
}))
OR
The same thing you can do in a wrapped react component that wrappes every page of your app.
class App extends React.Component {
componentDidMount() {
eventTrackingStore.getIpAddress().then(res => {
// set the ips into a store or any logic you want in order to pass them down to children
})
}
render() {
return this.props.children
}
}
I can think of two solutions:
You can use react context
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
Use context to share the store between all components and if the data is not loaded, initialize loading right there in that nested component.
If the data is already there then just take it,
getIpAddress method should return a promise, so in case when data is already there it will be immediately resolved.

When Is A Good Time To Use React Router Replace(componentDidMount?)

I have a component that if someone would copy the url and page it in a new tab would crash as it is missing data required from a pervious step.
I want to redirect them to some other page and remove it from their "back" history. So I was thinking of using "replace"
I been doing it on componentDidMount but noticed everything just seems to run twice.
// Page1Component
componentDidMount(){
if(true){
const { replace } = props.routingStore;
const params = props.match.params;
replace('/members/home')
}
}
So when the above code gets hit, this is what happens
Page1Component componentDidMount loads
HomeComponent componentDidMount loads
Page1Component componentDidMount loads again
HomeComponent componentDidMount loads
you can use the Redirect component directly in the render method :
if(...) {
return <Redirect to="/members/home" push={false}/>;
}
Note that using push={false} will replace the current entry into the history

is there a faster way to check the user's state in firebase like onAuthStateChanged?

I have currently working on a next.js project using react, redux and firebase.
When user enters a page that need authorization I use the following code to redirect them if they are not authenticated.
import React from 'react';
import Router from 'next/router';
import { firebase } from '../../firebase';
import * as routes from '../../constants/routes';
const withAuthorization = (needsAuthorization) => (Component) => {
class WithAuthorization extends React.Component {
componentDidMount() {
firebase.auth.onAuthStateChanged(authUser => {
if (!authUser && needsAuthorization) {
Router.push(routes.SIGN_IN)
}
});
}
render() {
return (
<Component { ...this.props } />
);
}
}
return WithAuthorization;
}
export default withAuthorization;
I am using the following repo as my base project. The problem is the app seems to work fine but when I try to navigate to a page that requires authentication when I am not authenticated. I am not redirected immediately rather it shows the page first then redirects. Since the HOC uses
firebase.auth.onAuthStateChanged()
which is asynchronous. Is there a faster way of checking if the user is logged in.
I have so far considered the
firebase.auth().currentUser
but my internal state depends on the update of the onAuthStateChanged function when the user logs out in another page.
Using a listener to onAuthStateChanged is usually the correct way to restore authentication state upon a full page transition. Since there is a reload happening at such a transition, there is a chance that the authentication state has changed. Using an onAuthStateChanged listener ensures that Firebase calls your code after it has validated the authentication state of the user. Since this may require a call to the server, this can indeed takes some time, so you'll want to show a "loading..." animation.
If the state transition doesn't require a reload, like when you're just rendering a new route in the same page, then you can be reasonably certain that the authentication state doesn't change on the transition. In that case you could pass the current user from the previous component into the new one. Tyler has a good article on this: Pass props to a component rendered by React Router.
In the latter case firebase.auth().currentUser should also retain its state, and you can use it safely. You'd then still use an onAuthStateChanged to detect changes in authentication state after the new route has loaded.

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'});
}

Resources