React: Authentification and avoiding repeating code components - javascript

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

Why am I getting "Objects are not valid as a React child" error?

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

How to render different Layouts in React?

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

Browser back button can not navigate in React Router v4

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.

React js Redirect to main page when you try to refresh other pages

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>
)

React-BreadCrumbs does not pick up variable from method

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

Categories

Resources