I'm trying to navigate to admin page if LoggedIn and admin is true while sending props to /admin but this isn't working. Can you help please?
export default function Auth() {
function login(e) {
e.preventDefault();
const data = { email, password };
axios
.post("http://localhost:3001/api/Login", data, { withCredentials: true })
.then((response) => {
if(!!response.data.loggedIn && !!response.data.admin){ return( <Navigate to="/admin" loggedIn={"response.data.loggedIn"} replace/> )}
else if(!!response.data.loggedIn && ! !!response.data.admin){ window.location.href = "https://www.dummyweb.com/webmail";}
else{return(alert("Username or Password is not valid!"))}
});
}
return (
<div>
<LogginForm/>
</div>
)
}
To conditionally render content or redirect then you should use the following:
react-router-dom
Since you are trying to use the useNavigate hook I'll assume you are using react-router-dom v6 and will start there.
Version 6
The Redirect component was removed in react-router-dom v6. Use the Navigate component and also specify the replace prop to do a redirect instead of a normal navigation.
export default function Admin(props) {
return props.isLoggedIn ? (
<div>
<Row className={stylesadmin.root}>
<Uploader/>
<ToastContainer/>
</Row>
</div>
) : (
<Navigate to="/Auth" replace />
);
}
Version 5
Use the Redirect component to redirect to "/Auth".
export default function Admin(props) {
return props.isLoggedIn ? (
<div>
<Row className={stylesadmin.root}>
<Uploader/>
<ToastContainer/>
</Row>
</div>
) : (
<Redirect to="/Auth" />
);
}
Update
Using imperative navigation/redirect.
Import the useNavigate (v6) / useHistory (v5) hook and issue imperative redirect.
export default function Auth() {
const navigate = useNavigate(); // v6
// const history = useHistory(); // v5
function login(e) {
e.preventDefault();
const data = { email, password };
axios
.post("http://localhost:3001/api/Login", data, { withCredentials: true })
.then((response) => {
if (!!response.data.loggedIn && !!response.data.admin) {
navigate(
"/admin",
{
replace: true,
state: { loggedIn: response.data.loggedIn },
}
);
// history.replace(
// "/admin",
// {
// loggedIn: response.data.loggedIn,
// }
// );
} else if (!!response.data.loggedIn && ! !!response.data.admin) {
window.location.href = "https://www.dummyweb.com/webmail";
} else {
alert("Username or Password is not valid!");
}
});
}
return (
<div>
<LogginForm/>
</div>
);
}
You can use react-router Redirect component
return <>{props.isLoggedIn ? (<div>
<Row className={stylesadmin.root}>
<Uploader/>
<ToastContainer/>
</Row>
</div>) : <Redirect to='/Auth'/> }</>
}
you can use either Redirect component or use useHistory hook from react-router-dom
import { useNavigate } from 'react-router-dom';
export default function Auth() {
let navigate = useNavigate();
function login(e) {
e.preventDefault();
const data = { email, password };
axios
.post("http://localhost:3001/api/Login", data, { withCredentials: true })
.then((response) => {
if(!!response.data.loggedIn && !!response.data.admin){ return( navigate( "/admin" , { state:{loggedIn:response.data.loggedIn}} )}
else if(!!response.data.loggedIn && ! !!response.data.admin){ window.location.href = "https://www.dummyweb.com/webmail";}
else{return(alert("Username or Password is not valid!"))}
});
}
return (
<div>
<LogginForm/>
</div>
)
}
and on the admin page I handle it as follows
export default function Admin() {
const {state} = useLocation();
const { loggedIn} = state;
return(
loggedIn?
(<whatever/>):(<whatever2/>)
)
}
This works perfectly
Related
import { useSession } from 'next-auth/react'
import { LoginPage } from '#/client/components/index'
const Homepage = () => {
const session = useSession()
if (session && session.data) {
return (
<>
<div>Homepage</div>
</>
)
}
return <LoginPage />
}
export default Homepage
Basically, I don't want to write the same boilerplate of Login & useSession() on every page.
I want something like:
import { withAuth } from '#/client/components/index'
const Homepage = () => {
return (
<>
<div>Homepage</div>
</>
)
}
export default withAuth(Homepage)
Or if possible withAuthHook?
I currently have done the following:
import React from 'react'
import { useSession } from 'next-auth/react'
import { LoginPage } from '#/client/components/index'
export const withAuth = (Component: React.Component) => (props) => {
const AuthenticatedComponent = () => {
const session = useSession()
if (session && session.data) {
return <Component {...props} />
}
return <LoginPage />
}
return AuthenticatedComponent
}
But I get an error:
JSX element type 'Component' does not have any construct or call signatures.ts(2604)
If I use React.ComponentType as mentioned in the answer below, I get an error saying:
TypeError: (0 , client_components_index__WEBPACK_IMPORTED_MODULE_0_.withAuth) is not a function
Have you tried:
export const withAuth = (Component: React.ComponentType) => (props) => {
...
https://flow.org/en/docs/react/types/#toc-react-componenttype
Edit:
Try like this:
export const withAuth = (Component: React.ComponentType) => (props) => {
const session = useSession()
if (session && session.data) {
return <Component {...props} />
}
return <LoginPage />
}
return AuthenticatedComponent
}
The answer was hidden in the docs. I had to specify the following Auth function in _app.tsx:
import { useEffect } from 'react'
import { AppProps } from 'next/app'
import { SessionProvider, signIn, useSession } from 'next-auth/react'
import { Provider } from 'urql'
import { client } from '#/client/graphql/client'
import '#/client/styles/index.css'
function Auth({ children }: { children: any }) {
const { data: session, status } = useSession()
const isUser = !!session?.user
useEffect(() => {
if (status === 'loading') return
if (!isUser) signIn()
}, [isUser, status])
if (isUser) {
return children
}
return <div>Loading...</div>
}
interface AppPropsWithAuth extends AppProps {
Component: AppProps['Component'] & { auth: boolean }
}
const CustomApp = ({ Component, pageProps: { session, ...pageProps } }: AppPropsWithAuth) => {
return (
<SessionProvider session={session}>
<Provider value={client}>
{Component.auth ? (
<Auth>
<Component {...pageProps} />
</Auth>
) : (
<Component {...pageProps} />
)}
</Provider>
</SessionProvider>
)
}
export default CustomApp
And on my actual page, I had to specify Component.auth as true:
const Homepage = () => {
return (
<>
<div>Homepage</div>
</>
)
}
Homepage.auth = true
export default Homepage
A nice summary of what it does can be found on https://simplernerd.com/next-auth-global-session
I have Login functionality as below -
function SignIn() {
const loginInfo = useSelector(state => state.loginDetails);
const iLoginCreds ={
userName:'',
password:'',
isLoggedIn:false
}
const dispatch = useDispatch();
const [loginCreds, setLoginCredentials] = useState(iLoginCreds)
useEffect(() => {
alert("state changed : "+loginCreds.isLoggedIn);
}, [loginCreds])
function checkIfSignedIn()
{
axios.get(`https://localhost:44301/api/login/ValidateLogin`)
.then(res=>{
console.log(JSON.stringify(res.data));
setLoginCredentials({
...loginCreds,
isLoggedIn:res.data
});
dispatch(StoreUserAuthenticationStatusAction(res.data));
});
}
if(loginInfo.isLoggedIn==true)
{
return (
<MainPage></MainPage>
)
}
else
{
return (
...
...
<FormGroup>
<Button style={{width:'100%',backgroundColor:"#FCB724",color:"black",fontWeight:"bold"}} onClick={checkIfSignedIn} >Sign in using our secure server</Button>
</FormGroup>
)
}
Reducer Index :-
import { combineReducers } from "redux";
import {SaveLoginStatusReducer} from "./LoginReducers"
export const reducers=combineReducers({
loginDetails:SaveLoginStatusReducer
})
Issue -
When if(loginInfo.isLoggedIn==true) , which I am fetching from useSelector at the beginning , I want to render to MainPage. But somehow cannot see , data is been fetched from central store. Page is not getting rendered to MainPage eventhough the state is been updated.
I am able to get alert for useEffect I have used when state changes. It shows "true".
EDIT 1 :-
When I am using if(loginCreds.isLoggedIn==true) , I am able to see MainPage , but when I try to retrieve it from store through if(loginInfo.isLoggedIn==true) , I dont get true.
Edit 2 :-
Action.js -
export const StoreUserAuthenticationStatusAction=(loginPayload)=>{
return {
type:'SaveLoginStatus',
payload:loginPayload
}
}
export const SetProductList=(productListPayload)=>{
return {
type:'SetProductList',
payload:productListPayload
}
}
Reducer.js -
const iLoginCreds ={
userName:'',
password:'',
isLoggedIn:false
}
export const SaveLoginStatusReducer =(state=iLoginCreds,action)=>{
switch (action.type) {
case 'SaveLoginStatus':
return {
...state,
user:action.paylod
}
break;
default:
return state;
}
}
Code pattern looks bad, you have to make authGuard to protect private routes.
That should redirects to auth page if the user is not signed in.
Redirects to main page after sign in.
const handleLogin = (e) => {
e.preventDefault();
if (user.email === email && user.password === password) {
login();
history.push("/main-page");
}
};
<Route
{...rest}
render={(props) =>
isLogin() ? <Component {...props} /> : <Redirect to="/login" />
}
/>
Problem: I am trying to authenticate a user through isAuth() helpers but it is acting weird. I want it to look for access token if any or call for access token from backend if refresh token is available, and though it works perfectly and sets access token cookie, the issue is if called from PrivateRoutes.jsx, it does not sees the tokens at all and sends the user to login page.
Adding required code for refs:
isAuth():
export const isAuth = () => {
if (window !== undefined) {
const accessCookieChecked = getCookie("_mar_accounts_at");
const refreshCookieChecked = getCookie("_mar_accounts_rt");
if (accessCookieChecked) {
return true;
} else if (refreshCookieChecked) {
console.log(refreshCookieChecked);
axios({
method: "POST",
url: `${API_URL}/api/token`,
data: { refresh_token: refreshCookieChecked },
}).then((res) => {
console.log(res);
setCookie("_mar_accounts_at", res.data.accessToken);
return true;
});
} else {
return false;
}
} else {
return false;
}
};
PrivateRoutes.jsx
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { isAuth } from "../helpers/auth";
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={(props) =>
isAuth() ? (
<Component {...props} />
) : (
<Redirect
to={{ pathname: "/login", state: { from: props.location } }}
/>
)
}
></Route>
);
export default PrivateRoute;
Can someone please see this? And help!
You are running into an async issue most likely, when you make the call in axios, the return true; in the callback never actually returns to your funciton call in the PrivateRoute. Instead, you need to use a Promise/setState/useEffect:
export const isAuth = () => {
if (window === undefined) {
return Promise.resolve(false);
} else {
const accessCookieChecked = getCookie("_mar_accounts_at");
const refreshCookieChecked = getCookie("_mar_accounts_rt");
if (accessCookieChecked) {
return Promise.resolve(true);
} else if (refreshCookieChecked) {
console.log(refreshCookieChecked);
return new Promise(resolve => {
axios({
method: "POST",
url: `${API_URL}/api/token`,
data: { refresh_token: refreshCookieChecked },
}).then((res) => {
console.log(res);
setCookie("_mar_accounts_at", res.data.accessToken);
resolve(true);
});
})
} else {
return Promise.resolve(false);
}
}
};
import React, { useState, useEffect } from 'react';
import { Route, Redirect } from 'react-router-dom';
import { isAuth } from '../helpers/auth';
const PrivateRoute = ({ component: Component, ...rest }) => {
const [isAuthTrue, setIsAuthTrue] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => {
isAuth().then(res => {
setIsAuthTrue(res);
setLoading(false);
})
})
return (
<>
{loading ? (
<div>some loading state</div>
) : (
<Route
{...rest}
render={(props) =>
isAuthTrue ? (
<Component {...props} />
) : (
<Redirect
to={{ pathname: '/login', state: { from: props.location } }}
/>
)
}
/>
)}
</>
);
};
export default PrivateRoute;
I use the axios post to request to the back-end if the user have access to the application. The problem is the axios returns undefined and then true or false . Have a private Route to manage what to do in case returns true or false (in this case undefined = false) ,is axios the problem or is there some other way? like wait until returns true or false
IsLogin.jsx
import React from 'react'
const axios = require('axios');
export const AuthContext = React.createContext({})
export default function Islogin({ children }) {
const isAuthenticated =()=>{
try{
axios.post('/api/auth').then(response => {
var res = response.data.result;
console.log(res)
return res
})
} catch (error) {
console.error(error);
return false
}
}
var auth = isAuthenticated()
console.log(auth);
return (
<AuthContext.Provider value={{auth}}>
{children}
</AuthContext.Provider>
)
}
privateRoute.js
import React, { useContext } from 'react';
import { Route, Redirect } from 'react-router-dom';
import {AuthContext} from '../utils/IsLogin';
const PrivateRoute = ({component: Component, ...rest}) => {
const {isAuthenticated} = useContext(AuthContext)
return (
// Show the component only when the user is logged in
// Otherwise, redirect the user to /unauth page
<Route {...rest} render={props => (
isAuthenticated ?
<Component {...props} />
: <Redirect to="/unauth" />
)} />
);
};
export default PrivateRoute;
app.js
class App extends Component {
render() {
return (
<>
<BrowserRouter>
<Islogin>
<Header/>
<Banner/>
<Switch>
<PrivateRoute exact path="/index" component={Landing} />
<PrivateRoute path="/upload" component={Upload} exact />
<PublicRoute restricted={false} path="/unauth" component={Unauthorized} exact />
</Switch>
</Islogin>
</BrowserRouter>
</>
);
}
}
You don't want to return anything in your post request. You should be updating your context store
const isAuthenticated = () => {
try {
axios.post('/api/auth').then(response => {
var res = response.data.result;
console.log(res)
// update your context here instead of returning
return res
})
} catch (error) {
console.error(error);
return false
}
}
In your private route, have a componentDidUpdate style useEffect hook to check for changes in authentication status and update an internal flag on an as-needed basis
const PrivateRoute = ({ component: Component, ...rest }) => {
const { isAuthenticated } = useContext(AuthContext)
const [validCredentials, setValidCredentials] = React.useState(false)
React.useEffect(() => {
if (typeof isAuthenticated === 'boolean') {
setValidCredentials(isAuthenticated)
}
}, [isAuthenticated])
return (
// Show the component only when the user is logged in
// Otherwise, redirect the user to /unauth page
<Route {...rest} render={props => (
validCredentials ?
<Component {...props} />
: <Redirect to="/unauth" />
)} />
);
};
I am curious as to why you didn't use 'async await',lol.
You are making a post request to the endpoint '/api/auth',but you didn't give it any data to post,like:
try{
axios.post('/api/auth',{username,password}).then(response => {
var res = response.data.result;
console.log(res)
return res
})
} catch (error) {
console.error(error);
return false
}
I am getting this warning every time I sign in,
Warning: Can't call setState (or forceUpdate) on an unmounted
component. This is a no-op, but it indicates a memory leak in your
application. To fix, cancel all subscriptions and asynchronous tasks
in the componentWillUnmount method.
Here is my code:
authpage.js
handleLoginSubmit = (e) => {
e.preventDefault()
let { email,password } = this.state
const data = {
email : email,
password : password
}
fetch('http://localhost:3001/auth/login',{
method : 'post',
body : JSON.stringify(data),
headers : {
"Content-Type":"application/json"
}
}).then(res => res.json())
.then(data => {
if(data.success){
sessionStorage.setItem('userid',data.user.id)
sessionStorage.setItem('email',data.user.email)
}
this.setState({loginData : data,
userData : data,
email:"",
password:""})
if(data.token) {
Auth.authenticateUser(data.token)
this.props.history.push('/dashboard')
}
this.handleLoginMessage()
this.isUserAuthenticated()
})
}
export default withRouter(AuthPage)
use withRouter so I can access props which I use to navigate this.props.history.push('/dashboard')
if I didn't use withRouter I cannot access this.props
index.js
const PrivateRoute = ({ component : Component, ...rest }) => {
return (
<Route {...rest} render = { props => (
Auth.isUserAuthenticated() ? (
<Component {...props} {...rest} />
) : (
<Redirect to = {{
pathname: '/',
state: { from: props.location }
}}/>
)
)}/>
)
}
const PropsRoute = ({ component : Component, ...rest }) => (
<Route {...rest} render = { props => (
<Component {...props} {...rest} />
)}/>
)
const Root = () => (
<BrowserRouter>
<Switch>
<PropsRoute exact path = "/" component = { AuthPage } />
<PrivateRoute path = "/dashboard" component = { DashboardPage } />
<Route path = "/logout" component = { LogoutFunction } />
<Route component = { () => <h1> Page Not Found </h1> } />
</Switch>
</BrowserRouter>
)
I think the problem is with my withRouter,
how can we access this.props without using withRouter ?
It's async so
this.setState({
loginData : data,
userData : data,
email:"",
password:""
})
make error
You can use this._mount to check component is mounted or unmount
componentDidMount () {
this._mounted = true
}
componentWillUnmount () {
this._mounted = false
}
...
if(this._mounted) {
this.setState({
loginData : data,
userData : data,
email:"",
password:""
})
...
You can use _isMount with overloading the setState function:
componentWillUnmount() {
this._isMount = false;
}
componentDidMount() {
this._isMount = true;
}
setState(params) {
if (this._isMount) {
super.setState(params);
}
}
I had some problems using this.setState({ any }).
Every time the component was built, it called a function that used Axios and the response made a this.setState({ any }).
My problem was solved as follows:
In the componentDidMount() function I call another function called initialize() that I left as async and through it I can call my function that performs fetch and this.setState({ any }).
componentDidMount() {
this.initialize();
}
myFunction = async () => {
const { data: any } = await AnyApi.fetchAny();
this.setState({ any });
}
initialize = async () => {
await this.myFunction();
}