My website have a few pages that is protected by login. My current solution to this is:
in app.js:
<div className="app">
<Provider store={store}>
<Router history={appHistory} onUpdate={fireTracking}>
<Route name="main" component={AppHandler}>
<Route name="home" path="/" component={HomePageHandler}/>
</Route>
</Router>
</Provider>
</div>
And then my HomePageHandler is:
export default class HomePageHandler extends BaseAuthorizedComponent {
render() {
return (
<div>hello</div>
)
}
}
As the HomePageHandler extends BaseAuthorizedComponent, which is defined as:
class BaseAuthorizedComponent extends Component {
componentWillMount() {
if (!this.props.user.signed_in) {
this.context.router.push('/signin')
}
}
}
HomePageHandler.contextTypes = {
router: React.PropTypes.object.isRequired,
}
function select(state) {
return {
user: state.user,
}
}
export default connect(select)(BaseAuthorizedComponent)
The redux's user object has a flag that indicates if the user is logged in or not. The idea is that on the homepage, before the component is mounted, the BaseAuthorizedComponent would have checked and redirect to signin page if user is not logged in. My idea is to let every page that requires authorization to extend BaseAuthorizedComponent.
However the following error happens when trying to load the homepage:
Error: Could not find "store" in either the context or props of "Connect(BaseAuthorizedComponent)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(BaseAuthorizedComponent)".
No idea how can I fix the problem while keeping the advantage of a single place to check authorization. Any thoughts? Thanks!
First of all you is better to use composition instead of inheritance https://reactjs.org/docs/composition-vs-inheritance.html
Next, you can add "push" action creator from react-router-redux (https://github.com/reactjs/react-router-redux) to mapDispatchToProps function:
function composeAuth = (ComposedComponent) => {
class BaseAuthorizedComponent extends React.Component {
// We use componentDidMount instead of componentWillMount, cause componentWillMount is deprecated https://medium.com/#baphemot/whats-new-in-react-16-3-d2c9b7b6193b
componentDidMount() {
if (!this.props.user.signed_in) {
this.props.push('/signin');
}
}
render() {
if (!this.props.user.signed_in) {
return null;
}
return <ComposedComponent {...this.props} />
}
}
return connect(state => ({user: state.user}), {push})(BaseAuthorizedComponent);
}
class HomePageHandler extends React.Component {
render() {
return (
<div>hello</div>
)
}
}
export default composeAuth(HomePageHandler);
How about this:
class CheckAuth extends React.Component{
state = {
auth: false
}
render(){
return(
{this.state.auth ? <div>Authorized user</div> : <div>Unauthorized user</div>}
)
}
}
function mapStateToProps(state){
return{
auth: state.auth
}
}
export default connect(mapStateToProps)(CheckAuth);
And then include it in your other components like so:
import CheckAuth from './CheckAuth';
...
class Home extends React.Component{
render(){
return(
<div>
<CheckAuth />
Hello world!!
</div>
)
}
}
export default Home;
After more research, the easiest way to satisfy my requirement is:
in an util file:
export function requireAuth(nextState, replace) {
// use your own method to check if user is logged in or not
if (!isLoggedIn()) {
replace({pathname: '/signin'});
}
}
and then import this method in the app.js file and use it:
<div className="app">
<Provider store={store}>
<Router history={appHistory} onUpdate={fireTracking}>
<Route name="main" component={AppHandler}>
<Route name="home" path="/" component={HomePageHandler} onEnter={requireAuth}/>
</Route>
</Router>
</Provider>
</div>
In this way if the user requires auth(isLoggedIn() is false), then it will redirect the page to /signin.
after some research, the best way I've seen is this:
<Route name="name"
path="/path"
component={THeWorkHandler}
onEnter={requireAuth}/>
And the requireAuth is put in a helper file:
export function requireAuth(nextState, replace) {
if (!(//logic to see if user is logged in )) {
replace({pathname: '/user/signin'});
}
}
This way if a onEnter requireAuth determines that the user is not authenticated, it will redirect to /user/signin page.
Related
I am trying to implement role based authentication as seen in this tutorial
REACT AUTHENTICATION TUTORIAL
This is my function for react-router-dom
<Switch>
<Route exact path="/addcloth" component={Authorization(AddCloth, [1], role, [storelist, sectionlist])} />
<Switch />
And this is my authorization function
export default function Authorization(WrappedComponent, allowedRoles, userType, property) {
return class WithAuthorization extends React.Component {
render() {
if (allowedRoles.includes(userType)) {
let Component = <WrappedComponent />;
// some code to add property elements into Component
return Component;
} else {
return (
<AccessDenied />
);
}
}
};
};
As storelist and sectionlist are 2 props for AddCloth component and I am trying to pass that into AddCloth. In the tutorial he didnt mention about the same.
You are almost there. You need to send it as object.
<Switch>
<Route exact path="/addcloth" component={Authorization(AddCloth, [1],
role, {storelist, sectionlist})} />
<Switch />
In HOC, destructure the props and assign to component.
export default function Authorization(WrappedComponent, allowedRoles, userType, props) {
return class WithAuthorization extends React.Component {
render() {
if (allowedRoles.includes(userType)) {
let Component = <WrappedComponent {...props} />;
// some code to add property elements into Component
return Component;
} else {
return (
<AccessDenied />
);
}
}
};
};
I have a onClick function to navigate to other page. I tried this.props.history.push("/SecondPage/ID/") and some examples but nothing worked out.
I have the component like this:
export class MainPage extends Component {
constructor(props) {
super(props);
}
render(){
return (
<div id="main" onClick={this.NavigatetoOtherPage.bind(this)}>
)
}
NavigatetoOtherPage(){
let ID = this.props.ID; // I need to pass the ID as a parameter.
//Here I need to navigate to other page using. I can use window.location.href but I need to use react router.
}
}
export default connect(state => {
return {
ID: state.Reducer.ID,
};
})(MainPage)
My app.js file like this
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Route exact path='/' component={MainPage}/>
<Route path='/SecondPage/:ID/' component = {SecondPage} />
</Provider>
);
}
}
My index.js page like this
export function renderPage() {
ReactDOM.render(
<Router>
<App />
</Router>
, document.getElementById('root'));
}
renderPage();
How can I navigate to second page without window.location.href
You can use the useHistory hook or the Link component given you are using react-router-dom
import React from "react";
import { useHistory, Link } from "react-router-dom";
// Then in your component
const MainPage = (props) => {
/**
* hooks
*/
const history = useHistory();
/**
* function
*/
const handleNavigation = () => {
let ID = props.ID; // I need to pass the ID as a parameter.
history.push(`/dashboard/${ID}`)
}
return (
<button id="main" onClick={() => history.push("/")}> Go to / </button>
<button id="main" onClick={() => handleNavigation()}> Go to dynamic page
</button>
<Link to={`/dashboard/${props.ID}`} className="some-styling">
Using Link
</Link>
);
};
// I have merged both implementations
export default MainPage;
// Edited: Based on the comment, the issue is "The history is not coming in the props."
// Then you could use `withRouter` HOC, and then there will be
// the `history` object in the wrapped component's props.
import {withRouter} from 'react-router-dom';
class MainPage extends React.Component {
render(){
console.log(this.props.history) // history object
return(<div />)
}
}
export default withRouter(MainPage)`
Wrote down a small sandbox. I guess this is what you are trying to achieve.
https://codesandbox.io/s/practical-tereshkova-ilbig?file=/src/App.js
i'm facing a small problem with my react app.
I'm using bluprintjs Toaster, and i need to display them on top of all other component, no matter what. Like this if there is an error during login or logout the user will always see the toast even if there is a redirection.
My problem is, that i have a middle component that is used to protect access to unAuthenticated user.
On my app class i have a ref to the Toaster and can easily call renderToaster to display a toast. So the method is working correctly.
But when i pass it to my ProtectedRoute and then to MyForm Component i can't call it in the MyFrom component.
From App -> ProtectedRoute -> MyForm if i print this.props i can see the renderToaster() Method, but i think the link from MyFrom -> ProtectedRoute -> App is somehow broken because on MyFrom i have the this.toaster is undefined error.
How can i call my parent parent method. Or how can i create a link between app and MyForm compenent passing through ProtectedRoute?
Thank you for your help.
My App class:
class App extends Component {
renderToaster(intent, message) {
this.toaster.show({
intent: intent,
message: message
});
}
<React.Fragment>
<NavBarComponent />
<Switch>
<ProtectedRoute
exact
path="/path1"
name="path1"
location="/path1"
renderToaster={this.renderToaster}
component={MyForm}
/>
<ProtectedRoute
exact
path="/path2"
name="path2"
location="/path2"
component={params => <MyForm {...params} renderToaster={this.renderToaster} />}
/>
</Switch>
<Toaster
position={Position.BOTTOM}
ref={element => {
this.toaster = element;
}}
/>
</React.Fragment>
}
My ProtectedRoute class:
import React, { Component } from 'react';
import { Route, Redirect } from 'react-router-dom';
import { AuthContext } from '../providers/AuthProvider';
class ProtectedRoute extends Component {
state = {};
render() {
const { component, ...rest } = this.props;
const Component = component;
return (
<AuthContext>
{({ user }) => {
return user ? (
<Route render={params => <Component {...params} {...rest} />} />
) : (
<Redirect to="/" />
);
}}
</AuthContext>
);
}
}
export default ProtectedRoute;
And on my last class (MyForm passed to the protected Route) i call my renderToaster Method like this:
/**
* Component did Mount
*/
componentDidMount() {
this.props.renderToaster(Intent.PRIMARY, 'helloo');
}
You either need to bind renderToaster in the class constructor:
constructor(){
this.renderToaser = this.renderToaster.bind(this);
}
or declare renderToaser as an ES7 class property.
renderToaster = (intent, message) => {
this.toaster.show({
intent: intent,
message: message
});
}
The problem is this in renderToaster isn't pointing where you think it is when the method is passed to the child component. If you use either of these methods, then this will refer back to the class.
See the official docs for more detail: https://reactjs.org/docs/handling-events.html
My project uses React-Redux Provider.
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
, document.getElementById('root'));
and
class App extends Component {
componentDidMount(){
API.getCategories().then((categories)=>{
this.props.dispatch(addCategories(categories))
})
API.getAllPosts().then(posts => {
console.log('getAllPosts', posts)
})
}
render() {
return (
<div className="App">
<Route exact path="/" render={()=>{
return (
<div>
{
this.props.categories.map((category)=>{
return (
<Link key={category.name} to={`/category/${category.name}`} params={{category: category.name}} >{category.name}</Link>
)
})
}
</div>
)
}}
/>
<Route path="/category/:category" component={Category} />
</div>
);
}
}
function mapStateToProps(x) {
return {
categories: x.categories
}
}
// export default App;
export default withRouter(connect(
mapStateToProps,
)(App))
From the above code and based on my experience from a previous project, the Category component's this.props should have a dispatch method that I can call the actions with but for some reason it is not there.
This is my Category Component:
class Category extends Component {
componentDidMount(){
console.log('this.props of Category', this.props)
var category = this.props.match.params.category
API.getPosts(category).then((posts)=>{
console.log('after getPosts', posts)
this.props.dispatch(addAllPosts(posts))
})
}
render(){
return <p>Category</p>
}
}
export default Category
What am I missing here???
You need to use the connect function from react-redux on your Category component so it has access to dispatch.
export default connect()(Category)
Also, it might just be simplified for SO, but App does not need to be wrapped in withRouter. This is only required if you need the router props injected into the component. Route does this automatically for any component it renders, which is why you don't need it on Category.
export default connect(mapStateToProps)(App)
How should I redirect an user to a different url when I cannot get an access to history props of react-router?
What I want to do is when an user clicks an log-out link on the navigation menu, the user get redirected to the root path '/'.
handleAuthentication(event) {
this.props.toggleAuthenticationStatus(() => {
// I want to redirect an user to the root path '/' in this callback function.
});
}
handleAuthentication method is called when an user clicks an login/logout link on the navigation menu.
toggleAuthenticationStatus(callback) {
this.setState((prevState, props) => {
return { isLoggedIn: !prevState.isLoggedIn }
},
callback()
);
}
Then, when handleAuthentication method in the NavigationMenu Component, it calls toggleAuthenticationStatus method in App Component that changes the state of Login/Logout and run callback function which is defined in the handleAuthentication method in the NavigationMenu Component.
Is it ok to run "window.location.href = '/'" directly?
Does it mess up the react-router history object???
Could anyone please how I should implement user redirect in a right way?
App Component
import React, { Component } from 'react';
import NavigationMenu from './NavigationMenu';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Secret from './Secret';
import Top from './Top';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
isLoggedIn: false
};
this.toggleAuthenticationStatus = this.toggleAuthenticationStatus.bind(this);
}
toggleAuthenticationStatus(callback) {
this.setState((prevState, props) => {
return { isLoggedIn: !prevState.isLoggedIn }
},
callback()
);
}
render() {
return (
<BrowserRouter>
<div>
<NavigationMenu isLoggedIn={this.state.isLoggedIn} toggleAuthenticationStatus={this.toggleAuthenticationStatus} />
<Switch>
<Route path='/secret' render={(props) => <Secret isLoggedIn={this.state.isLoggedIn} {...props} />} />
<Route path='/' component={Top} />
</Switch>
</div>
</BrowserRouter>
)
}
}
NavigationMenu Component
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
class NavigationMenu extends Component {
constructor(props) {
super(props);
this.handleAuthentication = this.handleAuthentication.bind(this);
}
handleAuthentication(event) {
this.props.toggleAuthenticationStatus(() => {
// I want to redirect an user to the root path '/' in this callback function.
});
}
render() {
return (
<ul>
<li><Link to='/'>Top</Link></li>
<li><Link to='/secret'>Secret</Link></li>
<li><Link to='/login' onClick={this.handleAuthentication}>
{this.props.isLoggedIn === true ? 'Logout' : 'Login'}
</Link></li>
</ul>
)
}
}
export default NavigationMenu;
I found the method 'withRouter' in react-router.
This seems the solution in my situation.
I'm going to try using it.
https://reacttraining.com/react-router/web/api/withRouter
You can get access to the history object’s properties and the closest
's match via the withRouter higher-order component. withRouter
will re-render its component every time the route changes with the
same props as render props: { match, location, history }.