All of my main react components have some parts like this:
export default class ExampleMain extends Component {
constructor(props) {
super(props)
this.state = {
isAuthenticated: Meteor.userId() !== null
}
}
componentWillMount() {
if (!this.state.isAuthenticated) browserHistory.push('/login')
}
componentDidUpdate() {
if (!this.state.isAuthenticated) browserHistory.push('/login')
}
}
With this I am checking if a user is logged in. If this is false, the user will be redirected to login route.
As this part is used in many components, I was thinking if I can optimize this to get a DRY code...
Update
I am using react router:
render((
<Router history={ browserHistory }>
<Route path='/' component={ App }>
<IndexRoute component={ Main } />
<Route path='login' component={ Login } />
<Route path='content/:id' component={ Content } />
</Route>
<Redirect from='*' to='/' />
</Router>
), document.getElementById('root'))
You can try something like this:
<Router history={ browserHistory }>
<Route path='/' component={ App }>
<IndexRoute component={ Main } />
<Route path='/login' component={ Login } />
<Route path='content/:id' component={ Content } />
</Route>
<Redirect from='*' to='/' />
</Router>
And in App, using withRouter to "inject" the router inside your component:
import { withRouter } from 'react-router';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: Meteor.userId() !== null
}
}
componentWillMount() {
if (!this.state.isAuthenticated) {
this.props.router.push('/login');
}
}
}
export default withRouter(App);
Maybe this helps you. I would tried to use any hook before routing. But you always can extend your own class with such functionality like that example
function requireAuth(nextState, replace) {
if (!auth.loggedIn()) {
replace({
pathname: '/login',
state: { nextPathname: nextState.location.pathname }
})
}
}
render((
<Router history={ browserHistory }>
<Route path='/' component={ App }>
<IndexRoute component={ Main } />
<Route path='login' component={ Login } />
<Route path='content/:id' component={ Content } onEnter={requireAuth} />
</Route>
<Redirect from='*' to='/' />
</Router>
), document.getElementById('root'))
To see full code follow link above.
Related
I have a lot of files, but I think the problem is coming from my authentication component in React. I basically want to only display a page if the user is logged in otherwise I want to the user to be redirected.
react-dom.development.js:14887 Uncaught Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, compare, WrappedComponent}). If you meant to render a collection of children, use an array instead.
requireAuth.js
// function that can wrap any component to determine if it is authenticated
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { push } from "#lagunovsky/redux-react-router"; // not sure if this correct
export default function requireAuth(Component) {
class AuthenticationComponent extends React.Component {
constructor(props) {
super(props);
this.checkAuth();
}
componentDidUpdate(prevProps, prevState) {
this.checkAuth();
}
checkAuth() {
// if not authenticated then it is redirected
if (!this.props.isAuthenticated) {
const redirectAfterLogin = this.props.location.pathname;
this.props.dispatch(push(`/login?next=${redirectAfterLogin}`));
}
}
// if authenticated then renders the component
render() {
return (
<div>
{this.props.isAuthenticated === true ? (
<Component {...this.props} />
) : null}
</div>
);
}
}
AuthenticationComponent.propTypes = {
isAuthenticated: PropTypes.bool.isRequired,
location: PropTypes.shape({
pathname: PropTypes.string.isRequired,
}).isRequired,
dispatch: PropTypes.func.isRequired,
};
// checks isAuthenticated from the auth store
const mapStateToProps = (state) => {
return {
isAuthenticated: state.auth.isAuthenticated,
token: state.auth.token,
};
};
return connect(mapStateToProps)(AuthenticationComponent);
}
App.js
class App extends Component {
render() {
return (
<div>
<Root>
<ToastContainer hideProgressBar={true} newestOnTop={true} />
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/closet" element={requireAuth(Closet)} />
<Route path="*" element={<NotFound />} />
</Routes>
</Root>
</div>
);
}
}
I have done some digging but I can't find a problem like this.
The error is because on this line:
<Route path="/closet" element={React.createComponent(requireAuth(Closet))} />
You're passing the actual class definition to the element prop and not an instance of the class (which would be the React component). To fix this, you can use React.createElement:
class App extends Component {
render() {
return (
<div>
<Root>
<ToastContainer hideProgressBar={true} newestOnTop={true} />
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/closet" element={React.createElement(requireAuth(Closet))} />
<Route path="*" element={<NotFound />} />
</Routes>
</Root>
</div>
);
}
}
Because Route's element props need a ReactNode,But requireAuth(Closet)'s type is () => React.ReactNode, you can change your App.js like this:
const AuthComponent = requireAuth(Closet);
class App extends Component {
render() {
return (
<div>
<Root>
<ToastContainer hideProgressBar={true} newestOnTop={true} />
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/closet" element={<AuthComponent />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Root>
</div>
);
}
}
I am trying to show different Layouts using React. I have Navbar with links. For every links (Service, Works, Contact...etc) I want to render Navbar, but for SignIn link I don't want to show it. So my code is following:
import React, { Component } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import PublicLayout from './components/layouts/PublicLayout';
import SigninLayout from './components/layouts/SigninLayout';
import Main from './components/pages/Main';
import Services from './components/pages/Services';
import Price from './components/pages/Price';
import Works from './components/pages/Works';
import Signin from './components/pages/Signin';
import NotFound from './components/pages/NotFound';
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<Switch>
<PublicLayout>
<Route exact path='/' component={Main} />
<Route exact path='/services' component={Services} />
<Route exact path='/prices' component={Price} />
<Route exact path='/works' component={Works} />
</PublicLayout>
<SigninLayout>
<Route exact path='/signin' component={Signin} />
</SigninLayout>
<Route path="*" component={NotFound} />
</Switch>
</div>
</BrowserRouter>
);
}
}
export default App;
I expect that SigninLayout should use for SignIn url. But I still see Navbar instead. This is my SigninLayout code:
import React, { Component } from 'react';
class SigninLayout extends Component {
state = {
}
render() {
return (
<div>
{ this.props.children }
</div>
);
}
}
export default SigninLayout;
And this is my SignIn component:
import React, { Component } from 'react';
class Signin extends Component {
state = {
}
render() {
return (
<div>
<h1>Войти</h1>
<form>
<input type="text" placeholder="укажите e-mail" />
<input type="text" placeholder="укажите пароль" />
<button>Войти</button>
</form>
</div>
);
}
}
export default Signin;
Why the Navbar is showing? What I'm doing wrong?
UPD:
import React, { Component } from 'react';
import Navbar from '../nav/Navbar';
class PublicLayout extends Component {
state = {
items: [
{ id: 1, name: 'Услуги', link: '/services' },
{ id: 2, name: 'Цены', link: '/prices' },
{ id: 3, name: 'Как это работает?', link: '/works' },
{ id: 4, name: 'Войти', link: '/signin' },
]
}
render() {
return (
<div>
<Navbar items={ this.state.items } />
{ this.props.children }
</div>
);
}
}
export default PublicLayout;
First of all let's use <Switch>, no need to evaluate any other route if you already found your one:
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<Switch>
<Route exact path='/signin' component={SigninLayout} />
<Route component={PublicLayout} />
</Switch>
</div>
</BrowserRouter>
);
}
}
Note that we're selecting between two layouts here, sub-routes will go there. This was your error: an outer component (PublicLayout and SigningLayout) will be rendered even if none of its children are visible (well...unless it's empty itself).
const PublicLayout = () => (
<Switch>
<Route exact path='/' component={Main} />
<Route exact path='/services' component={Services} />
<Route exact path='/prices' component={Price} />
<Route exact path='/works' component={Works} />
</Switch>
);
Parallel with SigningLayout should be easy. Of course that's just an example but should be trivial to (untested):
const App = () => (
<BrowserRouter>
<div className="App">
<Switch>
<Route exact path='/signin'>
<SigningLayout><SignIn /></SigningLayout>
</Route>
<Route>
<PublicLayout>
<Switch>
<Route exact path='/' component={Main} />
<Route exact path='/services' component={Services} />
<Route exact path='/prices' component={Price} />
<Route exact path='/works' component={Works} />
</Switch>
<PublicLayout>
</Route>
</Switch>
</div>
</BrowserRouter>
);
I would suggest a bit restructuring of Switch
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<Switch>
<SigninLayout>
<Route exact path='/signin' component={Signin} />
</SigninLayout>
<PublicLayout>
<Route exact path='/' component={Main} />
<Route exact path='/services' component={Services} />
<Route exact path='/prices' component={Price} />
<Route exact path='/works' component={Works} />
</PublicLayout>
<Route path="*" component={NotFound} />
</Switch>
</div>
</BrowserRouter>
);
}
}
my problem is as in the title.
I am using react v16 and react-router v4
I navigate to couple of pages after that i click on the browser back button. It takes me to last visited page not the last route that i navigated.
In my previous react project (react v15, react-router v3) it is working just great.
Here is my source code, please tell me my mistake.
Thank you.
index.js
import { HashRouter as Router } from 'react-router-dom';
import routes from 'routes/index';
render(
<Provider store={store}>
<div>
<Router children={routes}/>
<ReduxToastr
timeOut={2000}
newestOnTop={false}
preventDuplicates={false}
position="top-right"
transitionIn="fadeIn"
transitionOut="fadeOut"
progressBar={false}
showCloseButton={true}/>
</div>
</Provider>, window.document.getElementById('app'));
routes.js
export default (
<Switch>
<Route path="/login" component={Login} exact/>
<Route path="/logout" component={Logout} exact/>
<PrivateRoute path="/" component={Home} exact/>
<PrivateRoute path="/home" component={Home}/>
<PrivateRoute path="/apikeylist" component={ApiKeyList}/>
<PrivateRoute path="/apikey/new" component={ApiKeyAddUpdate}/>
<PrivateRoute path="/apikey/edit/:apiKey" component={ApiKeyAddUpdate}/>
<PrivateRoute path="/etf/promoter" component={EtfPromoter}/>
<PrivateRoute path="/etf/umbrella" component={EtfUmbrella}/>
<PrivateRoute path="/etf/fund" component={EtfFund}/>
<PrivateRoute path="/etf/shareclass" component={EtfShareclass}/>
<PrivateRoute path="/index/indexvariant" component={IndexVariant}/>
</Switch>
);
PrivateRoute.js
import React from 'react';
import PropTypes from 'prop-types';
import { Route } from 'react-router';
import App from 'layout/pages/App';
import { connect } from 'react-redux';
import Login from 'layout/pages/login';
import { withRouter } from 'react-router-dom';
class PrivateRoute extends React.Component {
constructor(props) {
super(props);
}
render() {
const { authenticated, component: Component, ...rest } = this.props;
return (
<Route {...rest} render={props => (
authenticated ? (
<App>
<Component {...props}/>
</App>
) : (
<Login/>
)
)}/>
);
}
}
PrivateRoute.propTypes = {
authenticated: PropTypes.bool,
component: PropTypes.any
};
const mapStateToProps = state => {
return {
authenticated: state.auth.authenticated
};
};
export default withRouter(connect(mapStateToProps)(PrivateRoute));
Removed replace props from <NavLink/> and its works.
replace attribute replaces your route with the current one. So it never keep the whole history only one route browser can remember.
Solved.
I need to redirect to main page when user refreshes other pages. It' s my attempt to add event listener for page refresh. But it doesn't work and I have Router.js?cf42:125 Uncaught RangeError: Maximum call stack size exceeded in browser. Could you advise me how to cope with this error?
import {browserHistory} from 'react-router';
class MyComponent extends React.Component {
constructor(props, context) {
super(props, context);
this.onUnload = this.onUnload.bind(this);
}
componentDidMount() {
window.addEventListener('beforeunload', this.onUnload);
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this.onUnload);
}
onUnload() {
browserHistory.push('/');
}
render() {
...
}
}
UPD: I have fixed RangeError. Now MyComponent's page is redirected to main page but after that it opens again. I think that this happens because redirect doesn't stop page refresh.
Try window.location.href instead of browserHistory.push. This will help.
onUnload() {
window.location.href = "/";
}
Here is an example:
https://stackblitz.com/edit/react-rbfoic?file=src/App.js
You need to bind onUnload() - add the following to your constructor:
this.onUnload = this.onUnload.bind(this);
Also, the correct event name for the listener is onbeforeunload
componentWillMount() {
sessionStorage.reload = true;
}
render() {
if(sessionStorage.reload && history.location.pathname!=='/') {
console.log(this.props);
sessionStorage.reload = "";
history.push('/login');
}
return (
<Router history={ history }>
<div className="App">
<Switch>
<>
<Route path="/" exact strict component={ Loginpage } />
<Route path="/mapview" exact component={ GoogleMap } />
<Route path="/dashboard" exact component={ Dashboard } />
<Route path="/tripList" exact component={ TripList } />
<Route path="/createTrips" exact component={ CreateTrips } />
<Route path="/trace" exact component={ trace } />
<Route path="/vehicleinfo" component={ vehicleinfo } />
<Route path="/MyPeople" component={ MyPeople } />
<Route path="/AddForm" component={ MyPeople } />
<Route path="/ListView" component={ ListView } />
<Route path="/fleetAlerts" component={FleetAlerts} />
<Route path="/login" exact component={ Loginpage } />
</>
</Switch>
</div>
</Router>
) }
try this simple way
import { useHistory } from "react-router-dom";
let history = useHistory();
function your(){
let confirm = () => {
alert(
`you will get redirect to home on click ok`
);
window.location.reload(history.push("/"));
};
return(
<button className="redirect_confirm" onClick={confirm}>
Confirm
</button>
)
}
If you are using React-Router and your home component path is /
onUnload() {
this.context.router.push('/');
}
Make sure your routes file is correct as well.
const routes = (
<Route path="/" component={App}>
<IndexRedirect to="/home" component{Home}/>
//all other routes
</Route>
)
I have a multi-page web application running with reactjs.
I'm trying to define a custom react-breadcrumb for a specific page
which involves extracting a value and using that in the breadcrumb
Running the code with the below, I can see that console picks up jobName
But if I look at the breadcrumbs, I'm stuck with
home > templates > Missing name prop from Route
I don't quite understand why the variable isn't being picked up for the router. If I just hard code it in, it will work. Any advice?
getTemplateJobName(templateId,dateChosen){
doGetJobById({jobId: templateId,reconDate: dateChosen}).then(
({body: template})=>{
let {jobName: jobName}=template;
console.log(jobName);
return jobName;
});
},
render(){
return (
<div>
<Router history={browserHistory}>
<Route name='home' path={'/'+contextName}>
<IndexRoute component={LandingPage}/>
<Route name='templates' path='templates'>
<IndexRoute component={JobPage}/>
<Route path=':reconDate&:templateId' component={JobDetailPage} staticName={true} getDisplayName={(params) => this.getTemplateJobName(params.templateId,params.reconDate)}/>
</Route>
<Route name='report' path='report' component={ReportPanel}/>
</Route>
<Route path='*' component={NotFoundPage}/>
</Router>
Try this please;
export default App extends React.Component {
constructor(props) {
super(props);
this.state = {
DisplayName: ''
};
}
getTemplateJobName(templateId,dateChosen) {
doGetJobById({jobId: templateId,reconDate: dateChosen}).then(
({body: template})=>{
let {jobName: jobName}=template;
console.log(jobName);
// return jobName;
this.setState({ DisplayName: jobName });
}
);
}
getDisplayName(params) {
this.getTemplateJobName(params.templateId,params.reconDate);
return this.state.DisplayName;
}
render() {
return
(<div>
<Router history={browserHistory}>
<Route name='home' path={'/' + contextName}>
<IndexRoute component={LandingPage}/>
<Route name='templates' path='templates'>
<IndexRoute component={JobPage}/>
<Route path=':reconDate&:templateId' component={JobDetailPage} staticName={true} getDisplayName={ this.getDisplayName.bind(this) }/>
</Route>
<Route name='report' path='report' component={ReportPanel}/>
</Route>
<Route path='*' component={NotFoundPage}/>
</Router>
</div>)
}
}