ReactJS: Handle Role for User - javascript

I have a page that I use to check the user authorization
const Routes = auth => {
return (
<div className="view-routes">
//...
<PrivateRoute
path="/"
component={Entities}
hasAnyAuthorities={[AUTHORITIES.USER, AUTHORITIES.ADMIN]}
/>
// so in this case, the hasAnyAuthorities check if the user has one of this role and return true so I will see the component Entities for this user.
)}
Now I need to add another user's role. So I have add :
const Routes = auth => {
return (
<div className="view-routes">
//...
<PrivateRoute
path="/"
component={Menu2}
hasAnyAuthorities={[AUTHORITIES.USER_APP]}
/>
<PrivateRoute
path="/"
component={Entities}
hasAnyAuthorities={[AUTHORITIES.USER, AUTHORITIES.ADMIN]}
/>
)}
In this way only the first component (menu2) is always shown. So if I enter with role "USER_APP" I can see the fields in component Menu2. But if I login with role: "ADMIN" I can't see fields inside Entities, as if only the first of the two PrivateRoute were controlled.
What would to obtain:
If I enter with role: USER_APP I'll see the fields in component Menu2.
If I ente with role ADMIN I'll see the fields in the component Entities.
How can I do?

Related

How can I make an asynchronous setState before following a Link?

I want to be able to 'capture' a selection of a user clicking a link that takes them to another page. I need the users selection to display a detail page of thebselected image.
I'm facing the problem that the browser follows the link before react updated the state "key". The state-Change of key is not passed to the details page. Is there an easy way to fix that without fetch?
export default class IGallery extends React.Component {
constructor(props) {
super(props);
this.state = {
countryList: [],
wallet: "",
key: "",
};
}
handleClick = (_key) => {
console.log("before setState", this.state.key);
this.setState({ key: _key }, () =>
console.log("after setState", this.state.key)
);
};
render() {
return (
<div className={styles.gallery}>
<h1>Gallery</h1>
<ImageList cols={6} rowHeight={320}>
{this.state.countryList.map((data, idx) => (
<ImageListItem key={idx} className={styles.imageItem}>
<Link to="/item" onClick={() => this.handleClick(data.key)}>
<img src={data.image} width={320} />
<ItemDetail id={this.state.key}></ItemDetail>
</Link>
<ImageListItemBar
title={"Braintube"}
subtitle={data.key}
actionIcon={<FavoriteBorder fontSize="large" color="pink" />}
actionPosition="top"
/>
</ImageListItem>
))}
</ImageList>
</div>
);
}
}
I expect that
<ItemDetail id={this.state.key}></ItemDetail>
passes the state value to the child component itemDetail.
Here is my Routing Path from index.js
<Router>
<Header childToParent={childToParent}/>
<Switch>
<Route path="/" exact={true}>
<Home></Home></Route>
<Route path="/project-space">
<ProjectSpace childToParent={wallet}></ProjectSpace>
</Route>
<Route path="/about">
<About></About></Route>
<Route path="/item"><ItemDetail></ItemDetail></Route>
</Switch>
<FooterMain></FooterMain>
</Router>
I think we need to take a step back and understand the React paradigm to answer this question.
In React, state goes only one way and is not retained when a component is unmounted. Right now, we have the following
Router > SomePage > IGallery (State = ....)
and we're trying to redirect to:
Router > ItemPage
As you can see here, moving away to ItemPage will drop state because Router will re-render and SomePage will be unmounted.
Therefore, we have two options:
Pass this item id in the url parameter which will then be handled by the next page
Move the state to the router parent and pass the state's setter + getter down to the page components (unrecommended)
For your situation, option one is more intuitive.

React Router v2.7 to v6 onEnter Migration

I am trying to migrate an application running on router v3, using the onEnter attribute for route after auth.
onEnter function in YAMain.jsx
static onEnter(store) {
return (nextState, replaceState, callback) => {
// Check if the user is logged in and allowed to make requests before letting them proceed
store.dispatch({
type: IS_LOGGED_IN,
onLoggedIn: () => {
store.dispatch({
type: GET_SELECTED_LOCATION_AND_CLASSROOM,
onSuccess: callback,
onFailure: () => {
// Get all of the required information from the store
const profile = getProfile(store.getState());
const selectedClassroom = getSelectedClassroom(store.getState());
const selectedLocation = getSelectedLocation(store.getState());
// No location has been selected by an admin, go to the locations page
if (profile.get('accessLevel') !== 'E' && !selectedLocation.get('id')) {
// Return early if this is the page we are going to
if (nextState.location.pathname.startsWith('/location')) {
return callback();
}
replaceState('/location');
return callback();
}
// No classroom has been selected by a user, go to the classrooms page
if (!selectedClassroom.get('id')) {
// Return early if this is the page we are going to
if (nextState.location.pathname.startsWith('/classroom')) {
return callback();
}
replaceState('/classroom');
return callback();
}
return callback();
}
});
},
onNotLoggedIn: () => {
replaceState('/login');
callback();
},
onFailure: (error) => {
if (isTimeGateError(error)) {
replaceState('/locked');
callback();
}
}
});
};
render function in YARouter.jsx, both classes extend component.
render() {
return (
<BrowserRouter>
<Routes>
{/* Handles the main logic and renders all but one of the pages */}
<Route
exact path="/"
element={<YAMain/>
}
onEnter={YAMain.onEnter(this.props.store)}
>
<Route path="/" element={YADashboard}/>
{/* Locations page displays the available list of locations */}
<Route
path="location"
element={YALocations}
/>
{/* Classrooms page displays the available list of classrooms */}
<Route
path="classroom"
element={YAClassrooms}
/>
this is not the entirety of the routing but should be enough to give you an idea of what's going on.
This is what I have now, I have tried a number of things suggested on various places. I'm trying to understand how I can either make this work so I can move on and work on this and make it proper later, OR make it proper now and fix this issue.
How can I go about ensuring proper redirection for user authentication, I've spent 2 days at work trying to figure anything out and am completely stuck.
Thanks.
If you are just looking for a way to call onEnter when the route is matched and rendered then I think calling it in a mounting useEffect hook in a wrapper component is probably what you are after.
Example:
const YAMainWrapper = ({ children, onEnter }) => {
useEffect(() => {
onEnter();
}, []);
return children;
};
...
render() {
return (
<BrowserRouter>
<Routes>
{/* Handles the main logic and renders all but one of the pages */}
<Route
path="/"
element={(
<YAMainWrapper onEnter={YAMain.onEnter(this.props.store)}>
<YAMain />
</YAMainWrapper>
)}
>
<Route path="/" element={<YADashboard />} />
{/* Locations page displays the available list of locations */}
<Route path="location" element={<YALocations />} />
{/* Classrooms page displays the available list of classrooms */}
<Route path="classroom" element={<YAClassrooms />} />
...
</Route>

How to redirect a user from django to react during email account activation

I have spent days looking for solution to this but I keep walking round in circles. I have a react application that is receiving API from django. Every part of the react app and django is working individually. With postman I can test the APIs account for email verification and the user gets verified. When I fill account registration form in react and submit it, the user is sent an email confirmation message.
When I click on the activation link I got 'Page not found 'error. How do I redirect the user to react since the email activation link is created in django and as such has django domain and not react domain.
React App
function App() {
return (
<Router>
<Header />
<main className="py-3">
<>
<Route path='/signup' component={SignUpScreen} />
<Route exact path='/activate/:uid/:token' component={ ActivateAccount} />
</>
</main>
</Router>
);
}
Account Activation Component
const ActivateAccount = ({ verify, match }) => {
const [verified, setVerified] = useState(false);
const dispatch = useDispatch()
const verify_account = e => {
const uid = match.params.uid;
const token = match.params.token;
dispatch(verify(uid, token));
setVerified(true);
};
if (verified) {
return <Redirect to='/' />
}
return (
<Container className=' auth-container pt-4'>
<Form className="auth-form">
<Button
className="auth-button btn btn-block w-100"
type="submit"
onClick={verify_account}
>
Activate Account
</Button>
</Form>
</Container>
)
}
export default connect(null, { verify })(ActivateAccount);
urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('djoser.urls')),
path('api/', include('djoser.urls.jwt')),
path('api/', include('accounts.urls')),
]
settings.py
DJOSER = {
'SEND_CONFIRMATION_EMAIL': True,
'USER_CREATE_PASSWORD_RETYPE': True,
'LOGIN_FIELD': 'email',
'SET_USERNAME_RETYPE': True,
'SET_PASSWORD_RETYPE': True,
'ACTIVATION_URL': 'activate/{uid}/{token}',
'SEND_ACTIVATION_EMAIL': True,
'SERIALIZERS': {
'user_create': 'accounts.serializers.UserCreateSerializer',
'user': 'accounts.serializers.UserCreateSerializer',
'current_user': 'accounts.serializers.UserCreateSerializer',
}
}
Please any help on how I can accomplish this is highly cherished
Seems to me that your best option is to have the generated activation url point to your React domain, have the information needed hashed in a url query param, and then call the /activate endpoint from within your React app.

How to recognize where from component is invoked to display proper text

I would like to reuse component which is uploading/adding products to the database,
sometimes product is type of finished product, and sometimes the product is part of unfinished product.
http://localhost:1000/finished-product/add
http://localhost:1000/unfinished-product/add
In my routes I've defined:
<Route exact path="/finished-product/add" component={AddProduct} />
<Route exact path="/unfinished-product/add" component={AddProduct} />
When my component AddProduct is loaded I would like to display proper text for example:
Please add unfinished product : or Please add finished product :
I just want to separate those texts and stuffs like that, so how could I recognize if I'm displaying component
from path="/unfinished-product/add" or from path="/finished-product/add".
Thanks guys
Cheers
Use Route props
As your component is rendered by a Route it gets the following prop :
this.props.location.pathname // => '/finished-product/add' || '/unfinished-product/add'
You can use it to write a condition.
const text = this.props.location.pathname === '/finished-product/add' ? 'Please add finished product' : 'Please add unfinished product';
References:
Route props: https://reacttraining.com/react-router/web/api/Route/route-props
location prop: https://reacttraining.com/react-router/web/api/location
Or use render function with a new prop
Another solution would be using render function in your routes passing a new prop to your component :
<Route exact path="/finished-product/add" render={routeProps => <AddProduct {...routeProps} finished />} />
<Route exact path="/unfinished-product/add" render={routeProps => <AddProduct {...routeProps} />} />
So then you have a finished boolean prop in your AddProduct component.
const text = this.props.finished ? 'Please add finished product' : 'Please add unfinished product';
Reference:
https://reacttraining.com/react-router/web/api/Route/render-func
This can be solved using query params /product/add?status='finished'
while defining routes,
<Route exact path="/product/add" component={AddProduct} />
and in component,
<Link to='/product/add?status="finished"'>Finished Product</Link>
<Link to='/product/add?status="unfinished"'>Unfinished Product</Link>

Passing both match param and props into react component when using routing

I have a functional react component and want to render some properties for a selected person in my component.
So I first map a simple list with links to each person
{props.persons.map((person, i) =>{
return(
<li key={i}>
<Link to={`/topics/${person.id}`}>{person.name}</Link>
</li>
)
})}
</ul>
then I make the route
<Route path={`/topics/:id`} render={() => <Topic persons={props.persons} />} />
as of now I just pass in the persons.
However I also want to be able to pass in the id, so I can find the specific person, and render information about that person. I have tried using the matching property, but that seems to prohibit me from passing in props as well.
Any tips to make a workaround?
Maybe it would be possible to just pass in the properties of the selected person?
EDIT:
Here is what i have tried so far.
const Topic = (props) =>{
console.log('props are ', props) //logs array of persons
return(
<div>
<h2>Topic</h2>
<p>I have been rendered</p>
</div>
)
}
const Topic = ({match}) =>{
console.log('match is ', match) //logs match parameter, but now i can't access persons
return(
<div>
<h2>Topic</h2>
<p>I have been rendered</p>
</div>
)
}
When you're using the render prop on a <Route> component, the render function is passed an object with the match, location, and history information.
https://reacttraining.com/react-router/web/api/Route/route-props
<Route
path={`/topics/:id`}
render={({match}) => (
<Topic id={match.params.id} persons={props.persons} />
)}
/>
You no need to pass id as a prop to Topic component of route so instead to get the id of path param you can do following in the component to get the id
In the Topic component
You can get the id using
props.match.params.id
According to https://reacttraining.com/react-router/web/api/Route:
All three render methods will be passed the same three route props
match
location
history
So in the render prop for the route, you should be able to reference the match and pass it along:
<Route
path={`/topics/:id`}
render={({match}) => (
<Topic
persons={props.persons}
id={match.params.id}
/>
)}
/>
This is because the render function is basically a functional component, and the first parameter to a functional component is the props. (However, you don't have to reference the first parameter as props, you could use renderProps, for example, or restructure it into what you need (match) like I did above)
And then in topic:
const Topic = ({id, persons}) =>{
console.log('id is ', id) //logs id parameter
console.log('persons is ', persons) //logs persons parameter
return(
<div>
<h2>Topic</h2>
<p>I have been rendered for id {id}</p>
<code>{JSON.stringify(persons)}</code>
</div>
)
}
You can use useRouteMatch
So in your example:
<Route path={`/topics/:id`} render={() => <Topic persons={props.persons} />} />
Becomes:
<Route exact path={`/topics/:id`} component={() => <Topic persons={props.persons} />}/>
And Topic.js becomes:
import { useRouteMatch } from "react-router-dom"; // Import this
const Topic = (props) =>{
let match =useRouteMatch('/topics/:id').url.split('/'); // Get url and separate with /
match=match[match.length-1]; // The id is at the end of link, so access last array index
console.log('match id is ', match);
console.log('persons is ', persons);
return(
<div>
<h2>Topic</h2>
<p>I have been rendered</p>
</div>
)
}

Categories

Resources