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>
)
}
}
Related
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.
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)),
}
I have a component that displays a list of movie genres generated from a Movies API. What I want is that when the user clicks on the specific genre it displays the movies from that specified genre.
My problem is that the only way I can think of doing this is to literally make a different component for each genre, make a different action creator for each genre in my Redux that does a GET request to the API for each genre, and set the Link to that component for the specified genre. That seems really time-consuming and inefficient.
Is there a way I can make the ShowGenres component display different movies depending on what genre the user clicks on in the Genre component or is the solution I thought of the only way?
Here's my Redux:
import {createStore, applyMiddleware} from "redux";
import axios from "axios";
import thunk from "redux-thunk";
export const displayGenres = () => {
return dispatch => {
axios.get("https://api.themoviedb.org/3/genre/movie/list?api_key=<api-key>&language=en-US").then(response => {
dispatch({
type: "DISPLAY_GENRES",
genres: response.data.genres
})
}).catch(err => {
console.log(err);
})
}
}
export const selectedGenre = id => {
return dispatch => {
axios.get(`https://api.themoviedb.org/3/discover/movie?api_key=<api-key>&language=en-US&include_adult=false&include_video=false&page=1&primary_release_year=2017&with_genres=9648`).then(response => {
dispatch({
type:"SELECTED_GENRE",
select: response.data.results
})
}).catch(err => {
console.log(err);
})
}
}
const reducer = (prevState = {}, action) => {
switch(action.type){
case "DISPLAY_GENRES":
return {
genres: action.genres
}
case "SELECTED_GENRE":
return {
select: action.select,
}
default:
return prevState
}
}
const store = createStore(reducer, applyMiddleware(thunk));
export default store;
Here's my Genres component that displays all the individual Genres for the user to choose:
import React, {Component} from "react";
import {connect} from "react-redux";
import {displayGenres} from "./redux";
import {Link} from "react-router-dom";
import Navbar from "./Navbar";
class Genres extends Component {
constructor(){
super();
}
componentDidMount(){
this.props.displayGenres();
}
render(){
const mappedGenres = this.props.genres && this.props.genres.map(genre => {
return (
<div className="mappedGenres">
<Link to="/showGenres">{genre.name}</Link>
</div>
)
})
return(
<div>
<Navbar/>
<div className="genre">
{mappedGenres}
</div>
</div>
)
}
}
export default connect(state => state, {displayGenres})(Genres);
And here's my ShowGenres component where I want to display movies depending on the Genre that the user clicks on in the Genre component:
import React, {Component} from "react";
import {connect} from "react-redux";
import {selectedGenre} from "./redux";
import {displayGenres} from "./redux";
class ShowGenres extends Component {
constructor(){
super();
}
componentDidMount(){
this.props.selectedGenre(this.mappedId);
}
render(){
const mappedId = this.props.genres && this.props.genres.map(id => {
return id.id;
})
const mappedSelected = this.props.select && this.props.select.map(genre => {
return (
<div>
<h1>{genre.title}</h1>
</div>
)
})
return(
<div>
{mappedSelected}
</div>
)
}
}
export default connect(state=> state, {displayGenres, selectedGenre})(ShowGenres);
One way you can do this is to have a method that takes input and sets the state with said input. Now where you go with it from here can vary.
You can have the second component nested inside the first, pass the method as a prop, and for each movie, add an on click event listener that fires the method from props, passing the genre of the movie you clicked as a parameter. The method has to be binded to the parent before being passed down to a child
Will write up an example and post shortly.
Here's an example. There's a minor bug somewhere, but the general idea is there. I've used it before but it's been almost a year since touching react. Forgive the sloppiness
const AllMovies = {
Horror : ['Tax Season', 'Family Dinner', 'DMV Trip 3'],
Comedy : ['Pasion of the Christ', 'The Earth is Flat'],
Romance : ['Me, Myself and Bacon', 'There\'s Something About Ice Cream']
}
class Movie
{
constructor(title, genre)
{
this.genre = genre;
this.title = title;
}
}
class ChildContainer extends React.Component
{
constructor(props)
{
super(props);
}
GenerateList()
{
let output = [];
for (var genre in AllMovies)
{
let movies = AllMovies[genre];
for (let i = 0; i < movies.length; i++)
{
output.push(new Movie(movies[i], genre))
}
}
return output.map(movie => {
return(
<li onClick={() => this.props.CallParent(movie.genre)}>
{movie}
</li>
)
})
}
render()
{
return(
<ul>
{this.GenerateList()}
</ul>
)
}
}
class ParentContainer extends React.Component
{
constructor(props)
{
super(props);
this.state = {
CurrentValue : []
}
this.PropMethod = this.PropMethod.bind(this);
}
PropMethod(newValue)
{
console.log('__GENRE__ : ', newValue);
let genre = AllMovies[newValue]
this.setState({
CurrentValue : genre
})
}
render() {
let genres = this.state.CurrentValue.map(val => {
return(<li>{val}</li>)
})
return(
<section>
<ul>
{genres}
</ul>
<ChildContainer
CallParent={this.PropMethod}
/>
</section>
)
}
}
class App extends React.Component
{
constructor(props)
{
super(props);
}
render() {
return(
<section>
<ParentContainer/>
</section>
)
}
}
React.render(<App />, document.getElementById('root'));
UPDATE
It should work now :D
There's another way. Remember that all of these components are classes. You can create an instance of both and pass one function to the others props as well. This will allow you to do it without nesting them in eachother.
I'm using AutobahnJS I don't know how to use the same connection in another component.
I decided to make it manually by passing the session argument to another component like this but that's doesn't work I don't know why
Main component:
class App extends Component {
componentWillMount(){
this.Call();
}
Call = () => {
var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:9000/', realm: 'realm1' });
connection.onopen = function Pass (session) {
console.log(session, 'This I want to use it in anothr component')
};
connection.open();
}
render() {
return (
<div>
<Two Pass={this.Pass} />
</div>
)
}
}
export default App;
Child Component
class Two extends Component {
this.props.Pass(session); // if I console.log session will get error
//How to console log session here
render() {
return (
<div>
Child component
</div>
)
}
}
export default Two;
What is the best way to use the same connection(session) of Autobahn in another component?
update
class App extends Component {
constructor(props) {
super(props);
this.state = {
pageNumber: 1
}
this.sessionVar = this.sessionVar.bind(this)
}
Maybe you can try something like this:
let sessionVar = undefined
class App extends Component {
componentWillMount(){
this.Call();
}
Call = () => {
var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:9000/', realm: 'realm1' });
connection.onopen = function Pass (session) {
console.log(session, 'This I want to use it in anothr component')
sessionVar = session
};
connection.open();
}
render() {
return (
<div>
<Two Pass={this.Pass} session={sessionVar} />
</div>
)
}
}
you can also use state and it will probably be easier that would look like this:
Call = () => {
var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:9000/', realm: 'realm1' });
connection.onopen = function Pass (session) {
console.log(session, 'This I want to use it in anothr component')
this.setState({session})
};
connection.open();
}
render() {
return (
<div>
<Two session={this.state.session} />
</div>
)
}
}
Then you can do this in your child component:
class Two extends Component {
componentDidMount(){
console.log(this.props.session)
}
render() {
return (
<div>
Child component
</div>
)
}
}
export default Two;
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()