Passing variables to components via a Route in React - javascript

I've got a basic admin app and I basically want to protect certain routes against the roles sent by the API when a user logs in via the Oauth2 protocol.
I have a route like...
<Route name="app" handler={App}>
<Route name="admin" path="/admin" roles={["admin", "superadmin"]} />
</Route>
Then I have an authentication component...
import React from 'react';
import SessionStore from '../stores/auth/SessionStore';
export default (ComposedComponent) => {
return class AuthenticatedComponent extends React.Component {
static willTransitionTo(transition) {
// If user isn't logged in, send 'em back to the login page
if (!SessionStore.isLoggedIn()) {
transition.redirect('/login', {}, {'nextPath' : transition.path});
} else if (this.rolesRequired) {
// Get all current users roles from session store.
let userRoles = SessionStore.roles;
// Iterate through user roles, if none match the required roles, send 'em away ta.
if (!this.rolesRequired.every(role => userRoles.indexOf(role) >= 0)) {
transition.redirect('/login', {}, { 'nextPath' : transition.path });
}
}
}
constructor(props) {
super(props);
this.state = this._getLoginState();
}
_getLoginState() {
return {
userLoggedIn: SessionStore.isLoggedIn(),
user: SessionStore.user,
token: SessionStore.token,
roles: SessionStore.roles
};
}
componentDidMount() {
this.changeListener = this._onChange.bind(this);
SessionStore.addChangeListener(this.changeListener);
}
_onChange() {
this.setState(this._getLoginState());
}
componentsWillUnmount() {
SessionStore.removeChangeListener(this.changeListener);
}
render() {
return (
<ComposedComponent
{...this.props}
user={this.state.user}
token={this.state.token}
roles={this.state.roles}
userLoggedIn={this.state.userLoggedIn} />
);
}
}
}
Then any components which need authenticating are passed into an instance of the AuthenticatedComponent, for example...
import React from 'react';
import RoleStore from '../../stores/user/RoleStore';
i
mport AdminActions from '../../actions/admin/AdminActions';
import AuthenticatedComponent from '../../components/AuthenticatedComponent';
import AdminMenu from '../../components/admin/AdminMenu';
import Template from '../template/BaseTemplate.react';
import RoleActions from '../../actions/user/RoleActions';
/**
* Admin
*
* #author Ewan Valentine
* #copyright 65twenty 2015
*/
export default AuthenticatedComponent(class Admin extends React.Component {
constructor() {
super();
this.state = {
users: [],
roles: []
};
this.onChange = this.onChange.bind(this);
}
onChange() {
this.setState({
roles: RoleStore.data,
users: UserListStore.data
});
}
render() {
return(
<Template>
<main>
<AdminMenu />
<h2>Admin Home</h2>
</main>
</Template>
);
}
});
I basically can't figure out the best approach for defining the required roles and there doesn't seem to be any way of accessing props on the Route component.

I had a similar issue, where I wanted to only show "Billing" link on top navbar if user belonged to 'admin' group.
I also had an Authentication component like you did, then I created an Authorization component, and several authorization policies depending on the required roles. Here is the code:
var Authorized = require('../auth/Authorized');
var AdminComponentPolicy = require('../auth/policies/AdminComponentPolicy');
<Authorized policy={AdminComponentPolicy} action="show" user= {this.props.user}>
...protected stuff
</Authorized>
Here is the code for the Authorized component:
//Authorized.jsx
import React from 'react';
var Authorized = React.createClass({
render: function() {
//checks if the informed policy returns true for given action and user.
if (this.props.policy.authorized(this.props.action, this.props.user)) {
return this.props.children;
} else {
return null;
}
}
});
module.exports = Authorized;
Here is the code for AdminComponentPolicy
//AdminComponentPolicy.js
class AdminComponentPolicy {
authorized(action, user) {
//return user.role === 'admin';
let _policies = {
//the 'show' action in this policy returns true for 'admin' users
show: function(record) {
return record.role === 'admin';
},
destroy: function(record) {
return this.show(record) || record.role === 'check if owner here';
},
};
return _policies[action](user);
}
}
export default new AdminComponentPolicy()

Related

How to pass data from one component to another component?

I have two js files, including login and sidebar.
In the login.js(class component), it will call the loginAction to perform an API call and get back response data(id, role, username).
import LoginAction from auth.js
...
handleSubmit(event) {
event.preventDefault();
var response = LoginAction(this.state)
}
LoginAction is in the auth.js
export function LoginAction(data) {
const loginName = data.loginName;
const password = data.password;
var response = Login(loginName, password).then(response => {
if (response.data.errorCode === "") {
sessionStorage.setItem("token", response.data.data.token)
return {passwordError: null, loginNameError: null, isLoggedOn: true, role: response.data.data.isAdmin};
} else {
return formatError(response.data);
}
})
return response;
};
Here is the Login which is in the authservice.js
export const Login = (loginName, password) => {
const postData = { loginName, password }
console.log(postData);
return Axios.post("http://localhost:8080/login", postData, header);
}
how to pass the response to sidebar.js(class component)? I want to display the user's role on the sidebar.
if (data.isLoggedOn) {
console.log("routing to /employee")
this.props.router.navigate("/employee", { state: { "id": id, "role": role } })
}
I have tried to use the state to pass the data, but the role cannot be loaded to the sidebar after the first loading. The role only displays when I load the sidebar page again.
You can create a state in a common parent component and pass down a function that sets that state. Here's a quick example where I define a state user in the App and define login() which sets its state. Then I pass login() as a prop to the login button, and pass user as a prop to the sidebar.
CodeSandbox
import React, { Component } from "react";
export default class App extends Component {
constructor(){
super();
this.state = {user: null}
}
login(user) {
this.setState({user})
}
render() {
return (
<div className="App">
<Login loginHandler={(user)=>this.login(user)}/>
<Sidebar user={this.state.user}/>
</div>
);
}
}
class Sidebar extends Component{
render(){
return(
<div className="sidebar">Hey {this.props.user}</div>
)
}
}
class Login extends Component{
render(){
return(
<button onClick={()=>this.props.loginHandler('Foo')}>Login</button>
)
}
}

how to get access to a prop from component in an Object

i have to check the current Pathname of my website to set the Steps for my React-Joyride guided Tour.
i have a Parent Component where the routes are defined and in which the Joyride tour is implemented.
the Tutorial Component implements step Objects to set the steps for the tour like this:
import Tutorial from './tutorial/tutorial'
class App extends Component {
constructor (props) {
super(props)
this.state= {
// state things
}
}
render () {
return (
<BrowserRouter>
<Tutorial
run={this.state.run}
stepIndex={this.state.stepIndex}
firstPartClicked={this.state.firstPartClicked}
secondPartClicked={this.state.secondPartClicked}
handleRestart={this.handleRestart}
handleEnd={this.handleEnd}
handleSteps={this.handleSteps}
handleFirstPart={this.handleFirstPart}
handleSecondPart={this.handleSecondPart}
handleClickedFalse={this.handleClickedFalse}
handleSetVisited={this.handleSetVisited}
handleCheckTourDone={this.handleCheckTourDone}
/>
// many other Routes
<Route
exact path='/matches/:matchId/' render={(props) => {
this.removeGlobalTimeRef()
const matchId = this.props.matchId
return this.checkLoginThen(this.gotoMatchDetails(props))
}}
/>
</BrowserRouter>
)
}
}
export default App
import matchSteps from '../tutorial/steps/matchSteps'
class Tutorial extends React.Component {
constructor (props) {
super(props)
this.state = {
isUpdatet: false,
steps: []
}
}
callback = (tour) => {
const { action, index, type, status } = tour
if ([STATUS.FINISHED].includes(status)) {
this.props.handleEnd()
this.props.handleClickedFalse()
if (this.props.location.pathname.startsWith('/matches/') && this.props.location.pathname.includes('sequence')) {
this.props.handleSetVisited()
}
} else if ([STATUS.SKIPPED].includes(status)) {
this.props.handleEnd()
this.props.handleClickedFalse()
this.props.handleSetVisited()
} else if (action === 'close') {
this.props.handleEnd()
this.props.handleClickedFalse()
} else if ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND].includes(type)) {
const step = index + (action === ACTIONS.PREV ? -1 : 1)
this.props.handleSteps(step)
}
}
render () {
let { steps } = this.state
const pathname = this.props.location.pathname
const siteSteps = [matchSteps, matchEditorSteps, matchEditorStepsOne, matchEditorStepsTwo,
matchSequenceSteps, matchSequenceStepsOne, matchSequenceStepsTwo, matchSiteSteps, matchSiteStepsOne, matchSiteStepsTwo]
for (let i = 0; i < siteSteps.length; i++) {
if (pathname === siteSteps[i].onSite && siteSteps[i].part === 'full') {
steps = siteSteps[i].steps
} else if (pathname === siteSteps[i].onSite && siteSteps[i].part === 'one') {
if (this.props.firstPartClicked === true) {
steps = siteSteps[i].steps
}
} else if (pathname === siteSteps[i].onSite && siteSteps[i].part === 'two') {
if (this.props.secondPartClicked === true) {
steps = siteSteps[i].steps
}
}
}
}
return (
<>
<Joyride
callback={this.callback}
run={this.props.run}
stepIndex={this.props.stepIndex}
steps={steps}
continuous
disableOverlayClose
spotlightClicks
showSkipButton
locale={{
back: <span>Zurück</span>,
last: (<span>Beenden</span>),
next: (<span>Weiter</span>)
}}
styles={{
options: {
primaryColor: '#2d98da'
}
}}
/>
</>
)
}
export default withRouter(Tutorial)
const matchSteps = {
name: 'matchSteps',
// onSite: '/matches/:matchId/', <-- HERE
part: 'full',
steps: [{
target: '.row',
title: '',
content: 'bla bla',
placement: 'center',
disableBeacon: true
}]
export default matchSteps
now i have to set the matchId in the step Object at onSite: '/matches/:matchId/' so i can check the pathname in the Tutorial Component.
i dont know how to do it correctly i've tested some ideas but the matchId was always undefined.
You can use react router hooks useParams to get the parameters of the current url
import { useParams } from "react-router-dom";
let { slug } = useParams();
or use react router hooks useLocation to get the current url as location object
import { useLocation } from "react-router-dom";
let location = useLocation();
Your question isn't clear to me. Well the question is clear but the code snippets are not.
There are only two data flows in javascript. Data flows down from parent to child via properties. Data can be passed back to a parent via a callback. You can chain that callback from child -> parent -> grandparent.
class grandparent extends Component {
getDecentData = (data) => { do something with data}
render(){
<Parent CB = {this.getDecentData}/>
}
}
class Parent extends Component {
render(){
<Child CB = {this.props.CB} />
}
}
class Child extends Component {
clickHandler=(e) => {
// do something to get data
this.props.CB(data)
}
render(){
<Control onClick={this.clickHandler} />
}
}

why <Redirect to='/vacations' /> Does not work

I am trying to make simple app with login form on the home page, which redirects then to vacations page. I faced a problem when trying to make /vacations page private. Here is the code:
import React, { Component } from 'react';
import { Redirect } from "react-router";
import axios from "axios"
class Nav extends Component {
constructor() {
super();
this.state = {
userName: '',
password: '',
}
this.userLogin = this.userLogin.bind(this);
}
userLogin() {
let userToChack = {
userName: this.state.userName,
password: this.state.password,
}
axios.post(`http://localhost:3000/api/login`, userToChack)
.then(res => {
if (res.data !== "") {
// if user name and password OK
document.getElementById(`helloUser`).innerText = `Hello ${this.state.userName}`;
document.getElementById(`login`).innerText = `Logout`;
return <Redirect to='/vacations' />
} else {
// if user name or password NOT OK
console.log("user can't login");
document.getElementById(`helloUser`).innerText = ``;
document.getElementById(`login`).innerText =`Login`;
return <Redirect to='/' />
}
})
}
}
You can't return Redirect outside render. If you want to redirect after some imperative code you should use history.push()
apiCheck = () =>{
fetchResource().then(res => this.props.history.push('/path'))
}
history object it's available when wrapping any component (that already is under Router) with withRouter HOC from react-router-dom
export default withRouter(MyComponent)
I don't think that you're actually rendering the component. You have to call userLogin() in your render method.

How to integrate Firebaseui auth with Redux

I've got a working auth configuration set up using firebaseui. I have a private landing page that I'd like to redirect the user to, but I'm not sure how to pass the credentialed response into my redux store.
I basically want to call the handleClickLogin method (currently hooked to a dummy button) of my Home component from my signInSuccess callback. In other words I'm trying to dispatch(login()); when I get a successfull signin, which in turn adds the flag to my redux store which I can then use to gate my private landing page. Since firebase.js is not in the component tree, I don't have access to dispatch here, so how do I get the response hooked in to my store?
firebase.js
const uiConfig = ({
// signInSuccessUrl: '/',
signInOptions: [
firebase.auth.EmailAuthProvider.PROVIDER_ID,
],
callbacks: {
signInSuccess: (resp) => <<<???>>>,
},
});
firebase.initializeApp(config);
const ui = new firebaseui.auth.AuthUI(firebase.auth());
export const startFirebaseUI = elementId => {
ui.start(elementId, uiConfig);
};
Home.jsx (stripped down)
export class Home extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
};
componentDidMount = () => {
startFirebaseUI('#firebaseui-auth-container');
}
handleClickLogin = () => {
const { dispatch } = this.props;
dispatch(login());
};
render() {
const { user } = this.props;
return (
<Background>
<HomeContainer>
<Button
onClick={this.handleClickLogin}
>
<Text ml={2}>Start</Text>
</Button>
<div id="firebaseui-auth-container" />
</HomeContainer>
</Background>
);
}
}
function mapStateToProps(state) {
return { user: state.user };
}
export default connect(mapStateToProps)(Home);
Somehow typing the question helped me figured it out. Just needed to import the store and the appropriate action, then dispatch it directly.
import { store } from 'store/index';
import { login } from 'actions/index';
callbacks: {
signInSuccess: (resp) => store.dispatch(login(resp)),
}

How to set up the api-platform react-admin graphql stack?

I'm setting up an Api-platform (graphql enabled) server and a react-admin client.
I create a resource on api-platform (name Domain). If i query GraphQL Playground app i get expected results.
After react-admin installed with the ra-data-graphql-simple package, the client try to connect and the client return an error "Unknown resource domains. Make sure it has been declared on your server side schema"
Here is my App.js code
import React, { Component } from 'react';
import { Admin, Resource, Delete } from 'react-admin';
import buildGraphQLProvider from 'ra-data-graphql-simple';
import myBuildQuery from './dataProvider';
import { DomainShow, DomainEdit, DomainCreate, DomainList } from './domain';
class App extends Component {
constructor() {
super();
this.state = { dataProvider: null };
}
componentDidMount() {
const introspectionOptions = {
include: ['Domains', 'Purchases'],
};
buildGraphQLProvider({
//buildQuery: myBuildQuery,
clientOptions: { uri: 'http://127.0.0.1:8000/api/graphql' },
introspection: introspectionOptions
})
.then(dataProvider => this.setState({ dataProvider }));
}
render() {
const { dataProvider } = this.state;
if (!dataProvider) {
return <div>Loading</div>;
}
return (
<Admin dataProvider={dataProvider}>
<Resource name="domains" list={ DomainList } create={ DomainCreate } show={ DomainShow } edit={ DomainEdit } title="Domains"/>
</Admin>
);
}
}
export default App;

Categories