Should component update in redux/react-router v4 - reactjs

I am passing a property from my "Root" to my a react-route, "Home".
When the "state" (state.loggedIn) of my app changes, "Home" is updated (the appearance changes as expected) but "shouldComponentUpdate" is not called.
I want to use "shouldComponentUpdate" to detect if a property ("loggedIn") changed in the Root, and do some extra work.
// Root.js
import {Provider} from 'react-redux';
import {
BrowserRouter as Router,
Redirect,
Route,
Switch} from 'react-router-dom';
...
render() {
const store = this.props.store;
const loggedIn = this.state.loggedIn;
const handleLogin = this.handleLogin;
const handleLogout = this.handleLogout;
return (
<Provider store={store}>
<Router history={browserHistory}>
<Switch>
<Route exact path="/" component={
(props) => (
<Home
history={props.history}
loggedIn={loggedIn}
handleLogout={handleLogout}/>)} />
<Route path="/login" component={
(props) => (
<Login
history={props.history}
handleLogin={handleLogin}/>)} />
<Route path="/workflow" component={
loggedIn ?
((props) => (
<Workflows
history={props.history}
match={props.match}/>)) :
((props) => (
<Redirect to={
{
pathname: '/',
state: {from: props.location},
}
}/>)) }/>
</Switch>
</Router>
</Provider>
);
}
// Home.js
shouldComponentUpdate(nextProps, nextState) {
console.log(nextProps);
console.log(nextState);
if(nextProps.loggedIn !== nextState.loggedIn) {
if(nextState.loggedIn) {
this.socket.connect();
} else {
this.socket.disconnect();
}
}
return true;
}

It looks like your component is being remounted after every state change. That would explain why the component is redrawn, but the lifecyle function shouldComponentUpdate() isn't called. If you stick a log statement in componentWillMount() you should see it being called multiple times.

Related

How to disable go back button in Browser

I am trying to limit user going back to the previous page,
It works but I can see previous page for millisecond and then current page reloads with
all API requests.
How can I prevent that behaviour?
import React, { Component } from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { createBrowserHistory } from "history";
export default class Main extends Component {
componentDidMount() {
// const history = useHistory();
const history = createBrowserHistory();
history.listen((newLocation, action) => {
if (action === "PUSH") {
if (
newLocation.pathname !== this.currentPathname ||
newLocation.search !== this.currentSearch
) {
// Save new location
this.currentPathname = newLocation.pathname;
this.currentSearch = newLocation.search;
// Clone location object and push it to history
history.push({
pathname: newLocation.pathname,
search: newLocation.search,
});
}
} else {
// Send user back if they try to navigate back
history.go(1);
}
});
}
render() {
return (
<div>
<BrowserRouter>
<Switch>
<Route exact path="/" component={Page1} />
<Route path="/main/page2" component={Page2} />
<Route path="/main/page3" component={Page3} />
<Route path="/main/page4" component={Page4} />
<Route path="/main/page5" component={Page5} />
</Switch>
</BrowserRouter>
</div>
);
}
}
Try set the routes replace:
<Route path="/main/page2" component={Page2} replace={true}/>
<Route path="/main/page3" component={Page3} replace={true}/>
<Route path="/main/page4" component={Page4} replace={true}/>
<Route path="/main/page5" component={Page5} replace={true}/>

Warning: You should not use <Route component> and <Route render> in the same route; <Route render> will be ignored

Hello i try to protected route if is no auth but it is not work
Warning: You should not use Route component and Route render in the same route; Route render will be ignored
App.js
import React, { Fragment, useEffect } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import NavBar from './component/Layout/NavBar';
import Landing from './component/Layout/Landing';
import Login from '../src/component/Auth/Login';
import Register from '../src/component/Auth/Register';
import Dashboard from './component/dashboard/Dashboard';
import Alert from './component/Auth/Alert';
import PrivateRoute from './component/routing/PrivateRoute';
import './App.css';
// Redux
import { Provider } from 'react-redux';
import store from './store';
import setAuthToken from './utils/token';
import { loadUser } from './action/auth';
if (localStorage.token) {
setAuthToken(localStorage.token);
}
const App = () => {
useEffect(() => {
store.dispatch(
loadUser());
}, []);
return (
<Provider store={store}>
<Router>
<Fragment>
<NavBar />
<Route exact path="/" component={Landing}></Route>
<section className="container">
<Alert />
<Switch>
<Route exact path="/login" component={Login}></Route>
<Route exact path="/register" component={Register}></Route>
<PrivateRoute exact path="/dashboard" component={Dashboard}></PrivateRoute>
</Switch>
</section>
</Fragment>
</Router>
</Provider>
);
};
export default App;
PrivateRoute.js
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Route, Redirect } from 'react-router-dom';
const PrivateRoute = ({
componet: Component,
auth: { isAuthenticated, loading },
...rest
}) => (
<Route
{...rest}
render={props =>
!isAuthenticated && !loading ? (
<Redirect to="/login" />
) : (
<Component {...props} />
)
}
/>
);
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps)(PrivateRoute);
Warning: You should not use "Route component" and "Route render" in the same route; "Route render" will be ignored
how can I fix it ?
From route rendering method:
There are 3 ways to render something with a <Route>:
- <Route component>
- <Route render>
- <Route children>
Each is useful in different circumstances. You should use only one of these props on a given
PrivateRoute contains both component and render prop. You can only use one rendering method but not both.
<PrivateRoute exact path="/dashboard" component={Dashboard}></PrivateRoute> // here
const PrivateRoute = ({
...
}) => (
<Route
render={props => ()} // here
/>
);
FIX
: Rename component prop to comp since it's acting as an HOC:
// rename prop to `comp`
<PrivateRoute exact path="/dashboard" comp={Dashboard}></PrivateRoute>
const PrivateRoute = ({
comp: Component, // use comp prop
auth: { isAuthenticated, loading },
...rest
}) => (
<Route
{...rest}
render={props =>
!isAuthenticated && !loading ? (
<Redirect to="/login" />
) : (
<Component {...props} />
)
}
/>
);
Use <Route render={() => <YourComponent />} /> instead of <Route component={YourComponent} />.
Don't combine two of those, react does not like that.

Routes requiring authentication in react another way to handle (PrivateRoutes)

I'm looking for a way to do some route protection with react-router-4. Looking at an example in the documentation, they create a Component which is rendered like this:
<PrivateRoute path="/protected" component={Protected} />
and the privateRoute component:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
fakeAuth.isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}}/>
)
)}/>
)
I don't really understand why I should want to pass the "Protected" component as a property and then have to take care of spreading all the ...props and ...rest.
Before I was reading this doc (and other example), I created the following code, which just nest the routes in another component which takes care of the authentication part.
Because my example (which seems to work perfectly well), looks way more simplistic, I must be missing something.
Are there any downsides on this approach?
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
import Nav from './Nav';
// dummy
const Auth = {
isAuthenticated: () => { return true; }
}
const Home = () => <h1>Home</h1>
const SignIn = () => <h1>SignIn</h1>
const About = () => <h1>About</h1>
class PrivateOne extends Component {
render() {
console.log(this.props);
return <h1>Private</h1>
}
}
const PrivateTwo = () => <h1>PrivateTwo</h1>
const PrivateThree = () => <h1>PrivateThree</h1>
const NotFound = () => <h1>404</h1>
const Private = ({isAuthenticated, children}) => {
return(
isAuthenticated ? (
<div>
<h1>Private</h1>
{children}
</div>
) : (
<Redirect to={{
pathname: '/sign_in',
}}/>
)
)
}
const App = () =>
<div>
<Router>
<div>
<Nav />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/sign_in" component={SignIn} />
<Private isAuthenticated={Auth.isAuthenticated()}> {/* or some state later on */}
<Route path="/private1" component={PrivateOne} />
<Route path="/private2" component={PrivateTwo} />
<Route path="/private3" component={PrivateThree} />
</Private>
<Route component={NotFound} />
</Switch>
</div>
</Router>
</div>
export default App;

How to redirect to another route

There is one need for url authentication:
import React from "react";
import { connect } from "react-redux";
import { Switch, Route, Redirect } from "react-router-dom";
...
const IndexRouter = ({ loggedIn }) => (
<Switch>
<Route
path="/"
render={() => (loggedIn ? <Redirect to="/dashboard" /> : <Login />)}
/>
<Route exact path="/dashboard" component={DashboardRouter} />
<Route exact path="/stock" component={StockRouter} />
</Switch>
);
export default connect(
state => ({
loggedIn: state.persist.loggedIn
}),
{}
)(IndexRouter);
The code means if I have not logged in, all of url are required from client will redirect to Login component. Other than that it will route to DashboardRouter.
The StockRouter is another route related with DashboardRouter.
The problem is that if I logged in. All the unspecific url (except /dashboard, /stock) I manually typed showing the /dashboard url without anything. The specific url such as /stock can show the component StockRouter directly.
You would need to write a PrivateRoute wrapper around your Route and change the order of Routes in IndexRouter, so that the Route with path / is matched at the last otherwise all routes will match / first and will not render correctly
const PrivateRoute = ({component: Component, loggedIn, ...rest }) => {
if(!loggedIn) {
return <Redirect to="/login" />
}
return <Route {...rest} component={Component}/>
}
}
}
const IndexRouter = ({ loggedIn }) => (
<Switch>
<PrivateRoute exact path="/dashboard" component={DashboardRouter} />
<PrivateRoute exact path="/stock" component={StockRouter} />
<Redirect to="/dashboard" />
</Switch>
);
For more details, check Performing Authentication on Routes with react-router-v4
Just create a history component like this :
import React from "react";
import {withRouter} from "react-router";
let globalHistory = null;
class HistoryComponent extends React.Component {
componentWillMount() {
const {history} = this.props;
globalHistory = history;
}
componentWillReceiveProps(nextProps) {
globalHistory = nextProps.history;
}
render() {
return null;
}
}
export const GlobalHistory = withRouter(HistoryComponent);
export default function gotoRoute(route) {
return globalHistory.push(route);
}
And then import into your component:
import gotoRoute from "../../history";
gotoRoute({
pathname: "/your_url_here",
state: {
id: this.state.id
}
});
And in index.js
import {GlobalHistory} from "./history";
ReactDOM.render((
<Provider store={store}>
<BrowserRouter >
<div>
<GlobalHistory/>
<App/>
</div>
</BrowserRouter>
</Provider>
), document.getElementById('root'));

React Router / Redux / HOC not working with 'render' prop

I am using react router v4 and I'm trying to wrap my head around a react-router / redux / HOC related issue. I have a higher order component working. The HOC itself is connect()-ed to redux store. This approach works perfectly if I wire it up in a <Route /> via a component prop: <Route path="/profile" component={ withAuth(Profile) } /> does work.
However, when I try to do the same with a <Route /> and a render prop it does not work: <Route path="/profile" render={ () => withAuth(Profile) } /> The console throws "Route.render(): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object." It does work when I omit the HOC: <Route path="/profile" render={ () => <Profile /> } /> so I suspect a problem with the HOC but I can't find it.
The reason I'm trying to use render is I'd like to pass additional props to the HOC. Besides it bugs me that I can't find the bug.
Can anybody with a fresh eye have a look and put me on the right path? Thanks!
/* === app.js === */
import React, { Component } from 'react';
import { Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import Header from './header';
import Home from './home';
import Content from './about';
import Profile from './profile';
import withAuth from './withAuth';
import store from '../reducers/store';
export default class App extends Component {
render() {
return (
<Provider store={store}>
<div className="mdl-grid">
<Header />
<main className="mdl-layout__content">
<div className="page-content">
<Route path="/" exact component={Home} />
<Route path="/about" component={Content} />
<Route path="/profile" render={ () => withAuth(Profile) } />
</div>
</main>
</div>
</Provider>
)
}
}
/* === withAuth.js (Higher order component) === */
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
const HOC = WrappedComponent => {
return class extends Component {
render() {
if (this.props.auth) {
return <WrappedComponent authenticated={this.props.auth} {...this.props} />
} else {
return <Redirect to="/" />
}
}
}
}
function mapStateToProps({ auth }) {
return { auth };
}
export default WrappedComponent => connect(mapStateToProps)( HOC(WrappedComponent) );
The reason it doesn't work is because, here
<Route path="/profile" render={ () => withAuth(Profile) } />
render is actually assigned a function withAuth and not the returned value. What you need to do is
const AuthProfile = withAuth(Profile);
export default class App extends Component {
render() {
return (
<Provider store={store}>
<div className="mdl-grid">
<Header />
<main className="mdl-layout__content">
<div className="page-content">
<Route path="/" exact component={Home} />
<Route path="/about" component={Content} />
<Route path="/profile" render={ (props) => <AuthProfile {...props}/> } />
</div>
</main>
</div>
</Provider>
)
}
}
The difference between
render={ () => withAuth(Profile) }
and
render={ (props) => <AuthProfile {...props}/> }
is that in the first case its an arrow function that is bound to the context. Whereas in the second case its a function returning a component
I think your problem is with the way you use <Redirect />, you have to put it in <Route />. Look at this example:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
fakeAuth.isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}}/>
)
)}/>
)

Resources