How to make component not re-render when navigating pages? - javascript

In my application there is a main home page that gets loaded and there is another page (Page2.js) I can navigate to via the navbar I have implemented. The problem is that when I go to Page2.js and then go back to the main page, it will re-render all of the components on the main page. I am wondering if there is a way to check the last pathname and say if it is equal to a certain value then all of the components should stay the same?
I have tried using the shouldComponentUpdate method in the navbar (since I do not want it to change) and it is pretty faulty in that it would not read prevProps.location as a value but undefined:
shouldComponentUpdate = (prevProps) => {
if (this.props.location !== prevProps.location) {
return false;
}
}
App.js holds the code for my routing in the application:
// imports here
class App extends Component {
render() {
const {location} = this.props;
return (
<Switch >
<DefaultLayoutRoute exact path="/">
<Redirect to={HomePage}/>
</DefaultLayoutRoute>
<Route exact path={SIGNOUT} render={() => {
auth0Client.signOut();
window.location.href = homeRedirect;
return null
}}>
</Route>
<Route exact path={LOGIN_SUCCESS} component={Callback}/>
<DefaultLayoutRoute exact path={HomePage} location={location.pathname} component={HomePage}
pageName="Office Production"/>
<DefaultLayoutRoute
exact
path={AGENTRANKING}
component={AgentRankings}
pageName="Agents"
/>
<DefaultLayoutRoute exact path={SETTINGS} component={Settings}/>
</Switch>
);
}
}
export default withRouter(App);
Expected Result: To be able to navigate to Page2.js and then back to the main page with all of the components rendered the way it was unless I manually refresh
Actual Results: I go back to the main page and all the components start to re-render.

Related

How to change state of component based on actions of parallel component in React (using React Router)?

I have my App.js file structured in a way where the header and footer are always present, regardless of the page the user is in. The issue is that when a user modifies some data in a parallel component, the link transfers the user to the default page but the data in the header does not get modified. The header only gets updated once i refresh the page after the actions in the other component have been set by the user. If it helps, the data is coming from localStorage. I've tried prop drilling and messing around with state but no luck. Refreshing the pages programmatically also gives me unwanted behavior.The route where the data is being modified is <Route path="/item/:id" render={(props) => <Item {...props} />}/> And here is the app.js file:
App.js File
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom';
function App(){
const currentYear = new Date().getFullYear();
return (
<Router>
<Header/> {/* <-- ALWAYS PRESENT */}
<Switch>
<Route path='/' exact component={Products} />
<Route path='/contact' component={Contact}/>
<Route path='/cart' component={Cart}/>
<Route path="/item/:id" render={(props) => <Item {...props} />}/>
</Switch>
<Footer date={currentYear} /> {/* <-- THIS AS WELL */}
</Router>
)
}
If anyone wants to see the structure of the code in the Header or Item components let me know, but I'm wondering if there is a way React Router can handle these types of actions.
React components only can render data they know about. This data consists of their props and their state. When some side effect occurs, you update data and react rerenders your components. If you want to share data between components, you can do it in three ways - store it higher in tree, use dedicated data management solution or duplicate it in some way.
Considering your situation, you have three options. First is to keep data your components interested in App. Second would be to use some external state management solution and do it in a way said solution works.
Since you keep this data in localStorage, you have third option. In your header you can subscribe to localStorage changes and update state of your header. Beware, that simply putting everything from localStorage into state on every render can be costly, so try do do it only when data definitely changed.

Have a single useEffect hook after any component has been rendered

So I have my routes that render a component:
const Layout = () => {
return (
<InitLayout>
<Switch>
<Redirect exact from='/' to='/home/recent' />
<Route path="/home/:category" exact component={Home}></Route>
<Route path="/about" exact component={About}></Route>
<Route path="/help" exact component={Help}></Route>
<Route path="/users/:userId" exact component={UserProfile}></Route>
<Route path="/ask" exact component={AskQuestion}></Route>
<Route path="/review" exact component={ReviewPost}></Route>
<Route path="/posts/:postId/review" exact component={ReviewPost}></Route>
<Route path="/users/:userId" exact component={UserProfile}></Route>
<Redirect from='*' to='/home/recent' />
</Switch>
</InitLayout>
);
};
In any of these components I would theoretically set the following effect:
useEffect(() => {
if (!isSsr) {
fetchPosts();
}
setSsrState({ isSsr: false }); // ==> Here
}, []);
This code will basically set an SSR state, to prevent the client to re-request data from the server when it has already been preloaded from the server.
Now this state needs to be set from any of the components, but I don't want to duplicate this code in all components. Forgetting to put it will result in bugs, so that is definitely a bad idea.
Is there a straightforward way to define an effect once, and have it called in every of the route's component?
After speaking with Trace, the issue was able to be resolved by wrapping the layout component with an HOC, which sets the SSR state to false.
Interestingly enough, by wrapping the component inside an HOC, this sets off the child components first before the HOC component. That way, we are able to first check on the child component level if the SSR is set. After the child component useEffect is ran, the HOC useEffect is then ran, which sets the SSR flag back to false.

React Router re-render route instead of re-render component

I'm working on an app that is using React Router and I noticed that when my Redux store changes state that the router is re-rendering the component the current route refers to rather than re-rendering the route itself.
To illustrate the problem; I have implemented a PrivateRoute that checks if a user is currently logged in. In it's most basic form it looks something like this:
const PrivateRoute = ({component: Component, ...rest}) => {
return <Route {...rest} render={(props) => {
const state = store.getState()
if (state.isAuthenticated) {
return <Component {...props}/>
}
else {
return <Redirect to={{pathname: '/login'}}/
}
}}/>
})
This works great since I can now say something like this:
<PrivateRoute path="/" component={HomePage}/>
However, I noticed that when the state of isAuthenticated changes that React Router is calling the render method on the HomePage component rather than that it re-renders the route. This means that the application will only do the authentication check when a user goes from some page to the home page but once on the home page, the check is no longer performed.
The only work around I have at the moment is to move the authentication check into the render function of the component (which is obviously not where it belongs).
How can I make React Router re-render the route rather than re-render the component the route refers to when the state changes?
I managed to solve the problem by using a Higher Order Component rather than implementing the authentication check in the route.
function withEnsureAuthentication(WrappedComponent) {
return class extends React.Component {
render() {
if (this.props.store.isAuthenticated === false) {
return <Redirect to={{pathname: '/login'}}/>
}
return <WrappedComponent {...this.props}/>
}
}
}
You can now use a normal Route but apply the withEnsureAuthentication to the component:
const HomePage = withEnsureAuthentication(Home)
<Route path="/home" component={HomePage}/>

React Router V4 protected private route with Redux-persist and React-snapshot

I'm implementing private route like so using React Router Route Component:
function PrivateRoute({component: Component, authed, emailVerified, ...rest}) {
return (
<Route
{...rest}
render={props =>
authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/', state: {from: props.location}}} />}/>
)
}
Expected Behavior:
authed is persisted through page refresh through using redux-persist
So on page refresh or reload, if authed prop is true then router should render <Component /> without ever going to path "/"
Actual Behavior which is the Problem:
With authed === true (persisted)
reloading the page or refreshing it leads to the following actions taking place(checked redux devtools)
the action:
"##router/LOCATION_CHANGE" runs and takes it to the correct secure route but then "##router/LOCATION_CHANGE" runs again and it redirects to "/" for a moment and finally
"##router/LOCATION_CHANGE" runs again and directs route back to the secure path, even though authed === true through all this in the redux devtools
Then: My guess was that this error has something to with my main App Component rendering before redux-persist has time to re-hydrate the Redux store.
So I tried doing the following:
I tried delaying my main App component render until my store is re-hydrated using redux-persist like so:
render() {
const {authed, authedId, location, rehydrationComplete} = this.props
return (
<div>
{ rehydrationComplete
? <MainContainer>
<Switch key={location.key} location={location}>
<Route exact={true} path='/' component={HomeContainer} />
<Route render={() => <h2> Oops. Page not found. </h2>} />
</Switch>
</MainContainer>
: <div>...Loading </div> }
</div>
)
}
This effectively fixes the issue above of the path changing when "##router/LOCATION_CHANGE" action runs(only Changes the path keys), However this leads to another Issue with React-snapshot Now: all the static generated html files from react-snapshot Now contain only ...Loading. I tried to set snapshotDelay of 8200 in the react-snapshot options but that didnt solve the issue.
Then:
I tried the following to delay React-snapshot call so that it renders html after the store has been rehydrated:
import {render as snapshotRender} from 'react-snapshot'
import {ConnectedRouter} from 'react-router-redux'
async function init() {
const store = await configureStore()
snapshotRender(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('root')
)
registerServiceWorker()
}
init()
But now i get the error: that 'render' from react-snapshot was never called. Did you replace the call to ReactDOM.render()?
I know this is a loaded question, but I want to effectively use these 3 libs(React-Router V4, Redux-persist, React-snapshot) together to serve protected routes without the mentioned errors.
I have something similar to you. Here I use React-Router V4 and a persist-like library.
Your router/routes doesn't need to be aware of the persist. They should rely on your redux's store. The persist should rehydrate your store with all the data.
I didn't see where you are using the PrivateRoute component in your example. Where is it?

React Router calls component constructor when switching routes

I'm using react and react-router for a SPA which has a navigation. The router is calling the react component 'constructor' every time a navigation item is switched. Here is the relevant bit of code:
class Home extends React.Component {
constructor(props) {
super(props);
console.log('component constructed!');
this.state = {
counter: 0
}
setInterval(() => this.setState({
counter: this.state.counter+1
}), 1000)
}
render() {
return (
<h3>Counter: {this.state.counter}</h3>
);
}
}
ReactDOM.render(
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path="profile" component={Profile} />
<Route path="settings" component={Settings} />
</Route>
</Router>,
document.getElementById('container')
);
Every time I switch from one tab to another, the counter starts from 0.
Obviously I understand that render() should be called every time its state changes or when router switches tabs, but why call constructor for change of tab?! Is that how react-router works, or am I doing something wrong?
This question has been asked here, but the accepted answer talks about 're-rendering' and not re-construction of the component.
The constructor is called every time that a component mounts. Each time that you navigate to the location /, the <Home> component will mount. When you navigate to another location, the <Home> component will unmount. If you want the state to be persistent across navigation, it should be kept higher up the tree.

Categories

Resources