I have a component I am using for authentication, if a user is not authenticated I want to push them to the login page.
A basic example of the setup is...
Auth Component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { auth } from '../../firebase';
import { replace } from 'react-router-redux';
export default (WrappedComponent, {}) => {
class Authentication extends Component {
componentWillMount() {
auth.onAuthStateChanged(user => {
if (user) {
// User is signed in.
console.log('USER IS SIGNED IN');
} else {
// No user is signed in.
console.log('USER IS NOT SIGNED IN');
this.props.dispatch(replace('login'));
}
});
}
render() {
return <WrappedComponent>{this.props.children}</WrappedComponent>;
}
}
return connect(
null,
null
)(Authentication);
};
Routes
import React from 'react';
import Loadable from 'react-loadable';
import { Router, Route, IndexRoute } from 'react-router';
import AuthenticationComponent from './containers/Authentication';
import App from './components/App';
const AsyncRoute = loader =>
Loadable({
loader,
loading: () => <h3>Loading...</h3>,
delay: 300,
});
const LandingPage = AsyncRoute(() =>
import(/* webpackPrefetch: true, webpackChunkName: "landingPage" */ './containers/LandingPage')
);
const Login = AsyncRoute(() =>
import(/* webpackPrefetch: true, webpackChunkName: "login" */ './containers/Login')
);
const NotYetImplemented = () => <h6>Not Yet Implemented...</h6>;
export default ({ history }) => (
<Router history={history}>
<Route path="/" component={AuthenticationComponent(App, {})}>
<IndexRoute component={LandingPage} />
</Route>
<Route path="/login" component={Login} />
</Router>
);
Currently, when Firebase reports the user is not authenticated, the route is updated and shows as http://localhost:3001/login however the LandingPage component is rendered.
If I refresh the page on /login I do then get the correct component.
I have swapped out replace for push but had the same result.
like #Boo said you have to use exact or you can use switch and Route instead like this
import { Switch, Route} from 'react-router-dom'
<Switch>
<Route path='/' component={ HomePage } exact />
<Route path='/login' component={ LogIn } exact />
</Switch>
and using
Redirect
you can do something like this (the code below) instead of dispatching action in redux and this will work for sure
import { Redirect } from 'react-router'
export default (WrappedComponent, {}) => {
class Authentication extends Component {
state = { redirect : false }
componentWillMount() {
auth.onAuthStateChanged(user => {
if (user) {
// User is signed in.
console.log('USER IS SIGNED IN');
} else {
// No user is signed in.
console.log('USER IS NOT SIGNED IN');
//this.props.dispatch(replace('login'));
this.setState({redirect : true})
}
});
}
render() {
const {redirect } = this.state
if(!redirect){
return <WrappedComponent>{this.props.children}</WrappedComponent>;
} else {
return <Redirect to='where ever you want' />
}
}
Related
I am on react 17.x and react-router-dom 5.2. After login, even though I do history.push('/), it doesn't navigate to /. But the URL is updated in the address bar correctly.
App.js:
import { useEffect, useState } from 'react';
import { BrowserRouter as Router, Switch, Route, Link, Redirect } from "react-router-dom";
import Login from './pages/Login';
import Dashboard from './pages/Dashboard';
import { isLoggedInServer } from "../src/utils/auth"
import history from "./utils/history"
import './App.css';
function App() {
const [isAuthed, setIsAuthed] = useState()
useEffect(() => {
if (typeof isAuthed === 'undefined') {
isLoggedInServer().then(function (flag) {
setIsAuthed(flag)
});
}
}, [])
if (typeof isAuthed === 'undefined') {
return (
<div>Please wait...</div>
)
}
if (!isAuthed) {
history.push('/login')
}
return (
<Router history={history}>
<Switch>
<Route path="/">
<Dashboard setIsAuthed={setIsAuthed} />
</Route>
<Route path="/login">
<Login setIsAuthed={setIsAuthed} />
</Route>
</Switch>
</Router>
);
}
export default App;
history.js:
import { createBrowserHistory } from "history";
export default createBrowserHistory();
auth.js
import React, { useEffect } from "react"
import api from "../utils/api"
export const login = () => {
//to be written
}
export const logout = () => {
// to be written
}
function isLoggedInServer() {
let promise = api().get("/api/is-alive")
.then(res => {
console.log(res.data)
return true
})
.catch(err => {
if (err.response) {
console.log(err.response.data.message)
} else if (err.request) {
// client never received a response, or request never left
} else {
// anything else
}
return false
})
return promise
}
export { isLoggedInServer }
Login.jsx (signIn function)
below function gets called when the 'Login' button is clicked.
const signIn = e => {
e.preventDefault()
api().get('/sanctum/csrf-cookie').then(() => {
api().post('/login', formInput).then(response => {
if (response.data.error) {
console.log(response.data.error)
} else {
login()
console.log('routing to /')
history.push('/') // <- this doesn't work
}
}).catch(err => {
if (err.response) {
setErr(err.response.data.message)
console.log(err.response.data.message)
} else if (err.request) {
// client never received a response, or request never left
} else {
// anything else
}
})
})
}
Dashboard.jsx
import React from "react"
const Dashboard = () => {
return (
<div>Dashboard</div>
)
}
export default Dashboard
Try this in app.js replace the return function with below code
return (
<Router history={history}>
<Switch>
<Route path="/login">
<Login setIsAuthed={setIsAuthed} />
</Route>
<Route path="/" exact>
<Dashboard setIsAuthed={setIsAuthed} />
</Route>
</Switch>
</Router>
);
I think the problem is due to that you navigate too soon before all the routers are rendered. Try to move push inside useEffect. or use Redirect component from React Router
useEffect(()=> {
if (!isAuthed) {
history.push('/login')
}
}, [isAuthed]);
I am using react hooks with redux in my project. In the login component from my action file when I try to redirect to another page i.e. another component. It is redirecting to the login component within a few seconds.
Here is the code:
authReducer.js
const authReducer = (state = iState, action) => {
switch (action.type) {
case "IS_LOGIN":
return {
...state,
isLogin: action.payload,
};
}
})
userAction.js
export const loginSubmit = (data, props) => {
return async (dispatch) => {
axios
.post(`${process.env.REACT_APP_API_URL}login`, data)
.then((result) => {
if (result.data.code == 200) {
dispatch({
type: "IS_LOGIN",
payload: {
data: result.data.data,
authToken: result.data.authToken,
},
});
localStorage.setItem("debateAccountToken", result.data.authToken);
localStorage.setItem("email", result.data.data.email);
localStorage.setItem("id", result.data.data.id);
toast.success("Logged in successfully");
// setInterval(() => {
props.history.push("/log");
// }, 3000);
} else {
toast.error("Email or password wrong!!");
}
})
.catch((err) => {
console.log("error .. ", err);
toast.error("Somethihng went wrong!!");
setInterval(() => {
window.location.reload();
}, 3000);
});
};
};
component file -> route of /log
import React from "react";
function LoginPage() {
return <div>hello</div>;
}
export default LoginPage;
route file
import React, { Component } from "react";
import { BrowserRouter, Route } from "react-router-dom";
import Login from "./components/UserLogin";
import Debate from "./components/debate/Debate";
import LandingPage from "./components/LandingPage";
import UserRegister from "./components/UserRegister";
import LoginPage from "./components/LoginPage";
export default class App extends Component {
render() {
return (
<div>
<BrowserRouter>
<Route exact path="/" component={LandingPage} />
<Route exact path="/register" component={UserRegister} />
<Route exact path="/debate" component={Debate} />
<Route path="/login" component={Login} />
<Route path="/log" component={LoginPage} />
</BrowserRouter>
</div>
);
}
}
From useraction it is redirecting to /log component but eventually it is returning back to login component too. Where might I be mistaken?
I am trying to use history.push method in my redux react app. Its working fine but the problem is my component won't change, do you guys know why?
route.js:
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import { history } from '../helper/history'
export default class route extends React.Component {
render() {
return (
<Provider store={store}>
<Router history={history}>
<Switch>
<Route exact path="/login" component={Login} />
<Route path="/error" component={Error} />
</Switch>
</Router>
</Provider>
)
}
}
Redux action.js where a history is called, this is where I dispatch my actions:
export const loginRequest = () => {
return {
type: userType.LOGIN_REQUEST,
}
}
export const loginSuccess = () => {
return {
type: userType.LOGIN_SUCCESS,
}
}
export const loginFailure = () => {
return {
type: userType.LOGIN_FAILURE,
}
}
export const fetchUser = (data) => {
return (dispatch) => {
dispatch(loginRequest)
axios
.post(env.API_URL + 'login', { email: data.email, password: data.password })
.then((res) => {
dispatch(loginSuccess(res.data.user))
history.push({
pathname: '/profile',
})
})
.catch((err) => {
dispatch(loginFailure(error))
})
}
}
As you are providing history props, you should use Router. See this, Router and BrowserRouter:
import { Router, Route, Switch } from 'react-router-dom'
instead of
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
I want to login user automatically if refresh_token exists in localStorage but my issue here is, i am not able to change state to authenticated initially when app start. I tried with componentWillMount but i am getting old state 1st time then i get updated state. <PrivateRoute> here getting called 2-3 time, don't know where i am making mistake.
Expected Flow
autoLogin
PrivateRoute with new state
App.js
import React, { Component } from 'react'
import WrappedLoginForm from './containers/Login'
import ChatApp from './containers/Chat'
import { connect } from 'react-redux'
import { checkAuthentication } from './store/actions/auth'
import PrivateRoute from './route'
import {
BrowserRouter as Router,
Route
} from "react-router-dom";
class App extends Component {
componentWillMount() {
this.props.autoLogin()
}
render() {
return (
<Router>
<Route path='/login' component={WrappedLoginForm} />
<PrivateRoute path="/" component={ChatApp} authed={this.props.isAuthenticated} />
</Router>
)
}
}
const mapStateToProps = state => {
return {
isAuthenticated: state.isAuthenticated,
loading: state.loading
}
}
const mapDispatchToProps = dispatch => {
return {
autoLogin: () => {
dispatch(checkAuthentication())
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App)
route.js
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const PrivateRoute = ({ component: Component, authed, ...rest }) => {
console.log('aaa')
return (<Route
render={props => (
authed
? <Component />
: <Redirect to="/login" />
)}
/>)
};
export default PrivateRoute;
action.js
export const checkAuthentication = () => {
return dispatch => {
dispatch(authStart())
let refresh_token = localStorage.getItem('refresh_token')
axiosInstance.post('/api/token/refresh/', {
refresh: refresh_token
}).then(res => {
if (res.status === 200) {
localStorage.setItem("access_token", res.data.access)
dispatch(loginSuccess())
}
}).catch(error => {
console.log("balle")
dispatch(loginFailed(error))
})
}
}
first thing make sure that in your store the isAuthenticated property is false by default.
For PrivateRoute use :
{this.props.isAuthenticated
? <PrivateRoute path="/" component={ChatApp} />
: null
}
instead of <PrivateRoute path="/" component={ChatApp} authed={this.props.isAuthenticated} />
I have a state that is getting the right object from my action but It won't seem to actually append to the state. Does anyone have any experience working with reducers in React-Redux that can lend a hand? I'm not sure why I can't return the new state.
Here is the code in progress I have at the moment.
import * as types from '../constants'
const defaultState = {}
const userReducer = (state = defaultState, action) =>{
switch(action.type){
case types.USER_LOGGED_IN:
console.log("in the logged in reducer")
console.log(action.cookie)
return {
...state,
cookie: action.cookie
}
case types.USER_LOGGED_OUT:
return state
default:
return state
}
}
export default userReducer
The console.log will actually print out the correct cookie value for me. Any ideas or help would be greatly appreciated.
per Request here is the container,
import React, { Component, PropTypes } from 'react'
import { routerActions } from 'react-router-redux'
import { connect } from 'react-redux'
import GoogleLogin from '../components/GoogleLogin'
import actions from '../actions/'
import cookie from 'react-cookie'
const login = actions.userActions.login
function select(state, ownProps) {
const isAuthenticated = state.user.cookie || false
console.log(isAuthenticated)
const redirect = ownProps.location.query.redirect || '/'
return {
isAuthenticated,
redirect
}
}
class LoginContainer extends Component {
componentWillMount() {
const { isAuthenticated, replace, redirect } = this.props
if (isAuthenticated) {
replace(redirect)
}
}
componentWillReceiveProps(nextProps) {
const { isAuthenticated, replace, redirect } = nextProps
const { isAuthenticated: wasAuthenticated } = this.props
if (!wasAuthenticated && isAuthenticated) {
replace(redirect)
}
}
onClick(e){
e.preventDefault()
//console.log("in the onClick")
var status = cookie.load("MarketingStatsApp",false)
if(!(status == undefined)){
const login = actions.userActions.login
this.props.login({
cookie: status
})
}
window.location.href='auth/google'
};
render() {
return (
<div>
<h1>Login using Google</h1>
<GoogleLogin onClick={this.onClick.bind(this)}/>
</div>
)
}
}
export default connect(select, { login, replace: routerActions.replace })(LoginContainer)
Here you can see the false get printed out the first time through and then the cookie get printed out. You can even see the action but I can't click into the arrows for that action and the the other ones don't display the updated state as how I expect it to be
Thanks for the help everyone
The problem ended up being on my double verification on my router page. I needed to re-bring in the action variable for it to launch properly. Here is the code for that
import { Router, Route, browserHistory,IndexRedirect } from 'react-router'
import React from 'react';
import Layout from './components/Layout'
import Home from './containers/Home'
import Login from './containers/LoginContainer'
import Dashboard from './containers/Dashboard'
import { UserIsAuthenticated} from './util/wrapper'
import cookie from 'react-cookie'
import axios from 'axios'
import actions from './actions'
import store from './reducers';
const Authenticated = UserIsAuthenticated((props) => props.children)
function doubleCheckGUID(guid){
return axios.post("/auth/guid",{guid: guid}).then(response => {
const login = actions.userActions.login
store.dispatch(login({
cookie: cookie.load("MarketingStatsApp",false)
}))
return true;
}).catch(error => {
return false;
})
}
if(cookie.load("MarketingStatsApp",false)){
doubleCheckGUID(cookie.load("MarketingStatsApp",false))
}
const AppRouter = () =>
<Router history={browserHistory} >
<Route path="/" component={Layout}>
<IndexRedirect to="/home" />
<Route path="home" component={Home} />
<Route path="login" component={Login}/>
<Route component={Authenticated} >
<Route path="dash" component={Dashboard} />
</Route>
</Route>
</Router>
export default AppRouter