I'm studying Nextjs, I come from React and a lot seems to be the same but I was lost with the authentication and private routes.
I looked for a lot of codes on the internet, but either they broke, they didn't make sense to me, or they didn't explain my doubts correctly. My scenario is basically:
I have an application
This application has public and private routes
Users need to login to access private routes
Private routes have the same Navbar
My questions are:
How to create private routes using ReactContext.
How to share the same NavBar between pages (Without having to place the NavBar component on each screen)
How to correctly authenticate a user with my own code using preferably ReactContext
How to reset the routes after authentication (The user is unable to return to the login screen if he clicks the back button on the browser)
How to correctly save the JWT token so that it saves the user's session for longer so that he does not need to log in again
My code is working so far, but I'm sure it is horrible and completely flawed.
I have the following files:
_app.js (Root of the Nextjs project)
index.js (Login page)
privateRoute.js (File that verifies if the user is logged in and allows or not his access)
userContext.js (File that saves user information to be accessed by other components and pages)
NavContext.js (File that checks whether someone is logged in to render the NavBar or not)
_app.js
function MyApp({ Component, pageProps }) {
return (
<UserContextFunc>
<NavContext>
<Component {...pageProps} />
</NavContext>
</UserContextFunc>
);
}
export default MyApp;
index.js
export default function App() {
const [loading, setLoading] = React.useState(false);
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const { setState } = React.useContext(UserContext);
const router = useRouter();
const login = () => {
setLoading(true);
fetch("/api/auth/login", {
method: "POST",
body: JSON.stringify({
email,
password,
}),
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
setLoading(false);
if (res.status === 200) {
res.json().then(({ token, roles }) => {
setState({ roles, token });
window.localStorage.setItem(
"rou",
btoa(unescape(encodeURIComponent(JSON.stringify(roles))))
);
window.localStorage.setItem("token", token);
router.replace("/app/adm/home");
});
}
})
.catch((err) => {
});
};
return (
<>
<ReactNotification />
<label htmlFor="exampleInputEmail1" className="form-label">
Email
</label>
<input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="form-control"
/>
<label htmlFor="exampleInputPassword1" className="form-label">
Senha
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="form-control"
/>
<button
onClick={() => login()}
type="button"
className="btn btn-primary btn-block"
>
{loading ? (
<div
className="spinner-border text-light spinner-border-sm"
role="status"
>
<span className="visually-hidden"></span>
</div>
) : (
"Login"
)}
</button>
</>
);
}
privateRoute.js
const indexPage = "/";
const withAuth = (Component) => {
const Auth = (props) => {
const { setState } = React.useContext(UserContext);
const router = useRouter();
React.useEffect(() => {
const token = window.localStorage.getItem("token");
var roles = window.localStorage.getItem("rou");
if (roles) {
roles = decodeURIComponent(escape(atob(roles)));
}
if (!token || !roles || token == "undefined") {
window.localStorage.removeItem("token");
window.localStorage.removeItem("rou");
return router.replace(indexPage);
} else {
setState({ roles, token });
}
}, []);
return <Component {...props} />;
};
if (Component.getInitialProps) {
Auth.getInitialProps = Component.getInitialProps;
}
return Auth;
};
export default withAuth;
userContext.js
export const UserContext = React.createContext();
export const UserContextFunc = ({ children }) => {
const [state, dispatch] = React.useReducer(
(prevState, action) => {
switch (action.type) {
case 'SET':
return {
...prevState,
...action.newState,
};
}
},
{
roles: []
}
);
const setState = newState => {
dispatch({ type: 'SET', newState });
}
const getState = async () => {
return state
}
return (
<UserContext.Provider value={{ getState, setState, state }}>
{children}
</UserContext.Provider>
);
}
NavContext.js
function NavContext(props) {
const { state } = React.useContext(UserContext);
return (
<>
{state.roles && state.token && <NavBar />}
{props.children}
</>
);
}
export default NavContext;
In private files I export them this way
import withPrivateRoute from "../../../utils/privateRoute";
...
export default withPrivateRoute(Dashboard);
I hope I managed to explain it well, I know it's a lot, but I didn't find any content explaining how to create a private route in Nextjs or how to authenticate correctly without using the authentication templates found in the Next documentation.
This code works, but as I said it seems completely wrong. I accept tips too.
Related
I am rying to update a 'user' from my useContext hook.
The use context checks with an axios call to see if the user is authenticated but would also handle the login and the logout.
here are my contexts and custom hooks I made to access those
authContextProvider.js
const AuthContext = React.createContext();
const LoginContext = React.createContext()
const LogoutContext = React.createContext();
const useAuth = () => {
return useContext(AuthContext);
}
const useLogin = () => {
return useContext(LoginContext)
}
const useLogout = () => {
return useContext(LogoutContext)
}
and this is my provider:
authContextProvider.js
const AuthProvider = ({children}) => {
const [auth, setAuth] = useState(false);
useEffect(() => {
axios.get("api/auth")
.then((result) => {
if(result.data.auth) {
setAuth(result.data.auth)
}
})
.catch(err => {
throw err;
})
}, [])
const login = (email, password) => {
axios.post('api/login', {
email: email,
password: password
})
.then((response) => {
console.log(response.data.auth)
setAuth(response.data.auth)
})
}
const logout = () => {
axios.get('api/logout')
.then(response => {
console.log("User has been logged out.")
setAuth(false)
})
}
return (
<AuthContext.Provider value={auth}>
<LoginContext.Provider value={login}>
<LogoutContext.Provider value={logout}>
{children}
</LogoutContext.Provider>
</LoginContext.Provider>
</AuthContext.Provider>
)
}
Problem:
In my login component, I would use the useLogin hook to trigger my API call to log in with an email and password. But I cannot seem to figure out how to pass those from my login component to my context as props.
Login.js
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
// this is where my email and password would be send to my context
const handleLogin = useLogin()
return (
<>
Login
<div className={'loginForm'}>
<label htmlFor="email">Email</label>
<input type="email" id={'email'} name={'email'} onChange={(e) => {setEmail(e.target.value)}}/>
<br/>
<label htmlFor="password">Password</label>
<input type="password" id={'password'} name={'password'} onChange={(e) => {setPassword(e.target.value)}}/>
<br/>
<button onClick={() => handleLogin}>Go !</button>
</div>
<br/>
<br/>
< />
);
}
My quesion
How do I pass those props?
Or is this not intended and should this be done differently
I want to understand why don't work properly the useEffect hook without the AbortionController.abort function.
I have a nested route in the app.js like:
<Route path='/profile' element={<PrivateRoute />}>
<Route path='/profile' element={<Profile />} />
</Route>
than the two component:
PrivateRoute:
import { Navigate, Outlet } from 'react-router-dom';
import { useAuthStatus } from '../hooks/useAuthStatus';
export default function PrivateRoute() {
const { loggedIn, checkingStatus } = useAuthStatus();
if (checkingStatus) return <h1>Loading...</h1>;
return loggedIn ? <Outlet /> : <Navigate to='/sign-in' />;
}
Profile:
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { getAuth, updateProfile } from 'firebase/auth';
import { updateDoc, doc } from 'firebase/firestore';
import { db } from '../firebase.config';
import { toast } from 'react-toastify';
export default function Profile() {
const auth = getAuth();
const [changeDetails, setChangeDetails] = useState(false);
const [formData, setFormData] = useState({
name: auth.currentUser.displayName,
email: auth.currentUser.email,
});
const { name, email } = formData;
const navigate = useNavigate();
const onLogout = () => {
auth.signOut();
navigate('/');
};
const onSubmit = async (e) => {
try {
if (auth.currentUser.displayName !== name) {
//update display name if fb
await updateProfile(auth.currentUser, {
displayName: name,
});
//update in firestore
const userRef = doc(db, 'users', auth.currentUser.uid);
await updateDoc(userRef, {
name,
});
}
} catch (error) {
toast.error('Could not update profile details');
}
};
const onChange = (e) => {
setFormData((prev) => ({
...prev,
[e.target.id]: e.target.value,
}));
};
return (
<div className='profile'>
<header className='profileHeader'>
<p className='pageHeader'>My Profile</p>
<button type='button' className='logOut' onClick={onLogout}>
Logout
</button>
</header>
<main>
<div className='profileDetailsHeader'>
<p className='profileDetailsText'>Personal Details</p>
<p
className='changePersonalDetails'
onClick={() => {
setChangeDetails((prev) => !prev);
changeDetails && onSubmit();
}}
>
{changeDetails ? 'done' : 'change'}
</p>
</div>
<div className='profileCard'>
<form>
<input
type='text'
id='name'
className={!changeDetails ? 'profileName' : 'profileNameActive'}
disabled={!changeDetails}
value={name}
onChange={onChange}
/>
<input
type='text'
id='email'
className={!changeDetails ? 'profileEmail' : 'profileEmailActive'}
disabled={!changeDetails}
value={email}
onChange={onChange}
/>
</form>
</div>
</main>
</div>
);
}
and the custom Hook:
import { useEffect, useState } from 'react';
import { getAuth, onAuthStateChanged } from 'firebase/auth';
export function useAuthStatus() {
const [loggedIn, setLoggedIn] = useState(false);
const [checkingStatus, setCheckingStatus] = useState(true);
useEffect(() => {
const abortCont = new AbortController();
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
setLoggedIn(true);
} else {
setLoggedIn(false);
}
setCheckingStatus(false);
});
// return () => abortCont.abort();
}, [setLoggedIn, setCheckingStatus]);
return { loggedIn, checkingStatus };
}
Can you explain me why do I get the error: Warning: Can't perform a React state update 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 a useEffect cleanup function.
I found the solution with the AbortController, but still don't understand what is the problem.
The error appears randomly, sometimes when I log in, sometimes when I'm not logged in, and try to go on the profile page. The app works fine, just want to understand what happens under the hood.
If I understand, if I'm not logged in, then it will rendered the 'Sign-in' page, if I'm logged in, then the 'Profile' page will be rendered, also there is a loading page but it's not the case. So, it's simple, if I'm logged in render this page, if not, render the other page. So where is the Problem? Why do I need the AbortController function?
onAuthStateChanges will listen forever in your useEffect hook. You need to unsubscribe every time the hook is run otherwise you will have these memory leaks. In your case the change of the users auth state will try to called setLoggedIn even when the component has been unmounted.
Looking at the documentation for onAuthStateChanged (https://firebase.google.com/docs/reference/js/v8/firebase.auth.Auth#onauthstatechanged) it returns a firebase.Unsubscribe.
You'll have to do something like this:
useEffect(() => {
const auth = getAuth();
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
setLoggedIn(true);
} else {
setLoggedIn(false);
}
setCheckingStatus(false);
});
return () => unsubscribe();
})
The callback you can optionally return in a useEffect hook is used for cleanup on subsequent calls.
We have UserContext which sets user object which we can use throughout application. Our UserContext keep executing every time and unnecessary making api call even though dependency hasn't changed.
import React, { useState, useEffect } from 'react'
import APIService from './utils/APIService';
import { getCookies } from './utils/Helper';
const UserContext = React.createContext();
const UserContextProvider = (props) => {
const [token, setToken] = useState(getCookies('UserToken'));
const [user, setUser] = useState(null);
useEffect(() => {
console.log('Inside userContext calling as token ', token)
fetchUserInfo();
}, [token]);
const fetchUserInfo = async() => {
if (token) {
let userRes = await APIService.get(`/user?token=${token}`);
console.log('User route called')
setUser(userRes.data);
}
}
/*
If user logoff or login, update token from child component
*/
const refreshToken = (newToken) => {
//token = newToken;
setToken(newToken);
fetchUserInfo()
}
return (
<UserContext.Provider value={{user, token, refreshToken}}>
{props.children}
</UserContext.Provider>
);
}
export { UserContextProvider, UserContext }
Whenever we navigate to different page in our react app, we are seeing "User" route being called every time even though token isn't updated. Our token changes only when user log off.
Our AppRouter looks like following;
import React from 'react';
import AppRouter from "./AppRouter";
import { Container } from 'react-bootstrap';
import Header from './components/Header';
import { ToastProvider, DefaultToastContainer } from 'react-toast-notifications';
import 'bootstrap/dist/css/bootstrap.css';
import './scss/styles.scss';
import { UserContextProvider } from './UserContextProvider';
export default function App() {
const ToastContainer = (props) => (
<DefaultToastContainer
className="toast-container"
style={{ zIndex:100,top:50 }}
{...props}
/>
);
return (
<UserContextProvider>
<ToastProvider autoDismiss={true} autoDismissTimeout={3000} components={{ ToastContainer }}>
<Container fluid>
<Header />
<AppRouter />
</Container>
</ToastProvider>
</UserContextProvider>
)
}
This is our internal app so we want user to be logged in for 30 days and they don't have to keep login every time. So when user login first time, we create a token for them and keep that token in cookies. So if user close the browser and come back again, we check token in cookies. If token exists, we make API call to fetch user information and setUser in our context. This is the part which isn't working and it keep calling our user api during navigation to each route in application.
Here is our login.js
import React, { useState, useContext } from 'react';
import { setCookies } from '../../utils/Helper';
import APIService from '../../utils/RestApiService';
import { UserContext } from '../../UserContextProvider';
import queryString from 'query-string';
import './_login.scss';
const Login = (props) => {
const [email, setEmail] = useState(null);
const [password, setPassword] = useState(null);
const [error, setError] = useState(null);
const { siteId } = props;
const { refreshToken} = useContext(UserContext);
const onKeyPress = (e) => {
if (e.which === 13) {
attemptLogin()
}
}
let params = queryString.parse(props.location.search)
let redirectTo = "/"
if (params && params.redirect)
redirectTo = params.redirect
const attemptLogin = async () => {
const payload = {
email: email,
password: password,
siteid: siteId
};
let response = await APIService.post('/login', payload);
console.log('response - ', response)
if (response.status === 200) {
const { data } = response;
setCookies('UserToken', data.token);
refreshToken(data.token)
window.location.replace(redirectTo);
}
else {
const { error } = response.data;
setError(error);
}
}
const renderErrors = () => {
return (
<div className="text-center login-error">
{error}
</div>
)
}
return (
<div className="login-parent">
<div className="container">
<div className="login-row row justify-content-center align-items-center">
<div className="login-column">
<div className="login-box">
<form className="login-form form">
<h3 className="login-form-header text-center">Login</h3>
<div className="form-group">
<label>Email:</label>
<br/>
<input
onChange={e => setEmail(e.target.value)}
placeholder="enter email address"
type="text"
onKeyPress={onKeyPress}
className="form-control"/>
</div>
<div className="form-group">
<label>Password:</label>
<br/>
<input
onChange={e => setPassword(e.target.value)}
placeholder="enter password"
type="password"
className="form-control"/>
</div>
<div className="form-group">
<button
className="btn btn-secondary btn-block"
onClick={attemptLogin}
type="button">
Login
</button>
</div>
{error ? renderErrors() : null}
</form>
</div>
</div>
</div>
</div>
</div>
)
}
export default Login;
Our userContext looks like below
import React, { useState, useEffect } from 'react'
import APIService from './utils/APIService';
import { getCookies } from './utils/Helper';
const UserContext = React.createContext();
const UserContextProvider = (props) => {
const [token, setToken] = useState(getCookies('UserToken'));
const [user, setUser] = useState(null);
useEffect(() => {
if (!token) return;
console.log('Inside userContext calling as token ', token)
fetchUserInfo();
}, [token]);
const fetchUserInfo = async() => {
if (token) {
let userRes = await APIService.get(`/user?token=${token}`);
console.log('User route called')
setUser(userRes.data);
}
}
/*
If user logoff or login, update token from child component
*/
const refreshToken = (newToken) => {
//token = newToken;
setToken(newToken);
fetchUserInfo()
}
return (
<UserContext.Provider value={{user, token, refreshToken}}>
{props.children}
</UserContext.Provider>
);
}
export { UserContextProvider, UserContext }
Our getCookies function which simply read cookies using universal-cookies package
export const getCookies = (name) => {
return cookies.get(name);
};
So I tried to replicate your issue using a CodeSandbox, and these are my findings based on your code:
Context:
Your context has a useEffect which depend on token. When you call refreshToken, you update the token which automatically triggers the useEffect and makes a call to fetchUserInfo. So you don't need to call fetchUserInfo after setToken in refreshToken. Your context would look like:
const UserContext = React.createContext();
const UserContextProvider = (props) => {
const [token, setToken] = useState(getCookies("UserToken"));
const [user, setUser] = useState(null);
useEffect(() => {
console.log("Inside userContext calling as token ", token);
fetchUserInfo();
}, [token]);
const fetchUserInfo = async () => {
if (token) {
let userRes = await APIService.get(`/user?token=${token}`);
console.log('User route called')
setUser(userRes.data);
}
};
const refreshToken = (newToken) => {
setToken(newToken);
};
return (
<UserContext.Provider value={{ user, token, refreshToken }}>
{props.children}
</UserContext.Provider>
);
};
export { UserContextProvider, UserContext };
Route:
Now coming to your routing, since you've not included code of AppRouter I had to make an assumption that you use react-router with Switch component. (As shown in CodeSandbox).
I see a line in your Login component which is window.location.replace(redirectTo);. When you do this, the entire page gets refreshed (reloaded?) and React triggers a re-render, which is why I suppose your context methods fire again.
Instead use the history API from react-router (Again, my assumption) like so,
let history = useHistory();
history.push(redirectTo);
Here's the sandbox if you want to play around:
Consider the following code:
const Home = () => {
const [user, setUser] = useState(null);
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(authUser => {
if(authUser) {
setUser(authUser);
} else {
setUser(null)
}
});
return () => unsubscribe();
}, []);
return (
<div>
{user ? (
<Hero />
) : (
<Login />
)}
</div>
)
}
export default Home
The Login component has all the functions which handles all the Sign Up, Login and Third-Party Authentications using Firebase.
The problems are:
When I reload the page and if the user is already logged in, it shows the component for some time, and then renders the component, which gives a bad UX.
Also, when I sign in using Google or Facebook, again this component is rendered before finally rendering the component.
Please throw some light into this issue. Your help will be highly appreciated!
Edit:
Problem 1 is solved, but problem 2 is not. Here is the relevant code for problem 2:
Login.js
<div style={{ marginBottom: "2%" }}>
<GoogleSignup />
</div>
GoogleSignup.js
import { GoogleLoginButton } from "react-social-login-buttons";
import firebase from "firebase";
import fire from "../fire";
const GoogleSignup = ({ extensionId }) => {
const OnSubmitButton = async () => {
var provider = new firebase.auth.GoogleAuthProvider();
fire
.auth()
.signInWithPopup(provider)
.then((result) => {
const credential = result.credential;
const token = credential.accessToken;
const user = result.user;
})
.catch((error) => {
console.log(error);
});
};
return (
<div>
<GoogleLoginButton
style={{ fontSize: "17px" }}
text={"Continue with Google"}
align={"center"}
onClick={OnSubmitButton}
/>
</div>
);
};
export default GoogleSignup;
These lines:
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(authUser => {
if(authUser) {
setUser(authUser);
} else {
setUser(null)
}
});
return () => unsubscribe();
}, []);
can be replaced with just:
useEffect(() => auth.onAuthStateChanged(setUser), []);
Next, instead of passing in just null to the useState, pass in current user.
const [user, setUser] = useState(null);
becomes
const [user, setUser] = useState(auth.currentUser);
This results in:
const Home = () => {
const [user, setUser] = useState(auth.currentUser);
useEffect(() => auth.onAuthStateChanged(setUser), []);
return (
<div>
{user ? (
<Hero />
) : (
<Login />
)}
</div>
)
}
export default Home
Personally, I tend to use undefined/null/firebase.auth.User using:
const Home = () => {
const [user, setUser] = useState(() => firebase.auth().currentUser || undefined);
const loadingUser = user === undefined;
useEffect(() => firebase.auth().onAuthStateChanged(setUser), []);
if (loadingUser)
return null; // or show loading icon, etc.
return (
<div>
{user ? (
<Hero />
) : (
<Login />
)}
</div>
)
}
export default Home
After the popup has closed, Firebase Authentication still needs to handle the authentication flow of exchanging the provider's authentication token for a Firebase User token. While this is taking place, you should show some form of loading screen in your component. In the below code sample, I change the "Continue with Google" text to "Signing in..." and disable the onClick events for each button while the sign in process takes place.
import { GoogleLoginButton } from "react-social-login-buttons";
import firebase from "firebase";
import fire from "../fire";
const PROVIDER_ID_GOOGLE = firebase.auth.GoogleAuthProvider.PROVIDER_ID;
const ignoreOnClick = () => {};
const GoogleSignup = ({ extensionId }) => {
const [activeSignInMethod, setActiveSignInMethod] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
if (activeSignInMethod === null)
return; // do nothing.
let disposed = false, provider;
switch (activeSignInMethod) {
case PROVIDER_ID_GOOGLE:
provider = new firebase.auth.GoogleAuthProvider();
break;
default:
// this is here to help catch when you've added a button
// but forgot to add the provider as a case above
setError("Unsupported authentication provider");
return;
}
fire.auth()
.signInWithPopup(provider)
.then((result) => {
// const credential = result.credential;
// const token = credential.accessToken;
// const user = result.user;
if (!disposed) {
setError(null);
setActiveSignInMethod(null);
}
})
.catch((error) => {
console.error(`Failed to sign in using ${activeSignInMethod}`, error);
if (!disposed) {
setError("Failed to sign in!");
setActiveSignInMethod(null);
}
});
return () => disposed = true; // <- this is to prevent any "updating destroyed component" errors
}, [activeSignInMethod]);
return (
{ error && (<div key="error">{error}</div>) }
<div key="signin-list">
<GoogleLoginButton
style={{ fontSize: "17px" }}
text={
activeSignInMethod == PROVIDER_ID_GOOGLE
? "Signing in..."
: "Continue with Google"
}
align={"center"}
onClick={
activeSignInMethod === null
? () => setActiveSignInMethod(PROVIDER_ID_GOOGLE)
: ignoreOnClick
}
/>
</div>
);
};
export default GoogleSignup;
I am just taking my first steps with react and redux.
I started the project first without redux and now I have decided to implement it with redux.
The login worked before I adapted it to redux.
ThunkMiddleware is applied
Now the problem:
When I click the login button, the logger or DevTools only shows LOGIN_FAILURE. The page reloads and displays the login again.
If I change this
onSubmit={() => props.login(username, password)}
to this
onSubmit={props.login(username, password)}
LOGIN_REQEST actions are spammed and finally (if the password is stored in the browser) LOGIN_SUCCESS. I get the actual content with correct data from the server.
What do I have to change to make the login work normally?
Thanks for your help
LoginComponent:
function Login(props) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
return (
<div>
<form onSubmit={() => props.login(username, password)}>
<TextField
onChange={e => setUsername(e.target.value)}
/>
<br/>
<TextField
onChange={e => setPassword(e.target.value)}
/>
<br/>
<Button type="submit">
Login
</Button>
</form>
</div>);
}
const mapDispatchToProps = dispatch => {
return {
login: (username, password) => dispatch(login(username, password))
}
};
export default connect(
null,
mapDispatchToProps
)(Login)
LoginAction
import {
LOGIN_FAILURE,
LOGIN_REQUEST,
LOGIN_SUCCESS
} from "./LoginTypes";
export const login = (username = '', password = '') => {
return (dispatch) => {
dispatch(loginRequest());
axios.post(`server`, {
//data
}).then(
(res) => {
dispatch(loginSuccess(res));
},
(err) => {
dispatch(loginFailure(err.message));
}
);
}
};
export const loginRequest = () =>{
return {
type: LOGIN_REQUEST
}
};
export const loginSuccess = tabs =>{
return {
type: LOGIN_SUCCESS,
payload: tabs
}
};
export const loginFailure = error =>{
return {
type: LOGIN_FAILURE,
payload: error
}
};
LoginReducer:
const LoginReducer = (state = initialState, action) => {
switch (action.type){
case LOGIN_REQUEST:
return {
...state,
loading: true
};
case LOGIN_SUCCESS:
let tabBars = populateArray1(action.payload);
let navIcons = populateArray2();
return{
...state,
loading: false,
tabBars: tabBars,
navIcons: navIcons,
isLoggedIn: true
};
case LOGIN_FAILURE:
return{
...state,
loading: false,
error: action.payload
};
default: return state;
}
};
component, which controls login and content:
function Main(props) {
if(props.auth){
return(
<NotLogin />
)
}
else{
return <Login />
}
}
Your login page is getting refresh/redirecting due to which its not handling the api request & its response properly. Please try this by updating your login component.
function Login(props) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleLogin = (event) => {
event.preventDefault()
props.login(username, password);
}
return (
<div>
<form onSubmit={handleLogin}>
<TextField
onChange={e => setUsername(e.target.value)}
/>
<br/>
<TextField
onChange={e => setPassword(e.target.value)}
/>
<br/>
<Button type="submit">
Login
</Button>
</form>
</div>);
}
After updating that, please make sure that you are getting correct value in props.auth through redux in your Main component. The Main component should have redux connection with redux auth state in your code.