useState not updating properly with axios and Promise - javascript

I have this code:
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const onSubmitHandler = (event) => {
event.preventDefault();
setLoading(true);
axios({
method: 'post',
url: `${process.env.REACT_APP_API_URL}users/auth/sign_in`,
data: { email, password },
headers: { 'content-type': 'application/json' },
})
.then(function (response) {
setSuccess(true);
console.log(response, ' sucesso');
const authData = {
accessToken: response.headers['access-token'],
client: response.headers.client,
uid: response.headers.uid,
};
localStorage.setItem('authData', JSON.stringify(authData));
})
.catch(function (error) {
setLoading(false);
setError(true);
console.log(error, 'error');
});
};
if(success) return <Redirect to="/" />
It is a login page and this function gets executed when the user press the Login button. It works just fine but I'm having some trouble to make it redirect to the main page.
I have added a success const as useState and everytime the request is successful, it was supposed to set success to true with setSuccess(true) and redirect the user to / using react-router-dom but it is not working. The state still as false. What I'm doing wrong?
I have also tried using useEffect like this:
const returnFunc = () => <Redirect to="/" />;
useEffect(() => {
if (success) {
returnFunc();
}
}, [success, loading]);

I think you should just do:
useEffect(() => {}, [success, loading]);
if(success) return <Redirect to="/" />
If you are calling your returnFunc() in the UseState it is not going to work, just with nothing inside the UseEffect but only [success] is going to re-render the page because you set success to true and then it should take you to the main page.

Can you try this
return success ? <Redirect to="/" /> : "Render somthing for none login state";

Fixed adding window.location.href = '/'; to the .then and added an if to check if there is localStorage.
if (localStorage.getItem('authData')) {
return <Redirect to="/" />;
}

...
import { BrowserRouter as Router } from "react-router-dom";
...
return (
...
<Router>
{success && <Redirect to="/" />}
</Router>
)
// or you can try this way
import { useHistory } from "react-router-dom";
let history = useHistory();
...
useEffect(() => {
if (success) {
setSuccess(false)
history.push('/');
}
}, [success]);

Related

React state not updating inside custom route

I am having an issue where my react state is not updating.
I am trying to make a role-based protected route, following this tutorial style https://dev.to/iamandrewluca/private-route-in-react-router-v6-lg5, using the following component:
const MasterRoute = ({ children }) => {
const [role, setRole] = useState('');
const [isLoading, setIsLoading] = useState(false);
const checkAuth = async () => {
setIsLoading(true);
let response = await getRole();
setRole(response.role);
setIsLoading(false);
}
useEffect(() => {
checkAuth();
}, [])
useEffect(() => {
console.log(role);
}, [role])
return role === 'ADMIN' ? children : <Navigate to="/" />;
}
Logging the role in the useEffect function displays an empty result in the console.
Logging the variable response directly after the await function displays the correct response retrieved from the server.
I've tried to console log the role directly after the checkAuth() function in useEffect(), but also obtained an empty line in the console.
What could be the problem?
This component is used as the following in App.js file:
<Route
element={
<MasterRoute>
<Dashboard child={<Admin />}></Dashboard>
</MasterRoute>
}
path={'/roles'}
></Route>
Issue
It seems the general problem is that the initial role state is '', and since '' === 'ADMIN' evaluates false the <Navigate to="/" /> is rendered and the route changes. In other words, the route changed and MasterRoute likely isn't being rendered when the checkAuth call completes.
Solution
You could use that isLoading state to conditionally render null or some loading indicator while the auth/role status us checked. You'll want MasterRoute to mount with isLoading initially true so no routing/navigation action is taken on the initial render cycle.
Example:
const MasterRoute = ({ children }) => {
const [role, setRole] = useState('');
const [isLoading, setIsLoading] = useState(true); // <-- initially true
const checkAuth = async () => {
setIsLoading(true);
let response = await getRole();
setRole(response.role);
setIsLoading(false);
}
useEffect(() => {
checkAuth();
}, []);
useEffect(() => {
console.log(role);
}, [role]);
if (isLoading) {
return null; // or loading indicator/spinner/etc
}
return role === 'ADMIN' ? children : <Navigate to="/" replace />;
}

How to redirect the user to the home page when he logs in successfully in react?

I'm fairly new to redux toolkit so I'm still having a few issues with it!
I'm stuck on this. I couldn't find what to do, how to follow a method. so i need your help.
My only request is to redirect the user to the home page when he logs in successfully, but I couldn't find what to check, where and how. I will share the code pages that I think will help you.
import { createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
import { TokenService } from "./token.service";
export const AuthService = {};
AuthService.userLogin = createAsyncThunk(
"userSlice/userLogin",
async ({ username, password }) => {
console.log(username, password);
try {
const response = await axios.post(
"https://localhost:7163/api/Login",
{ username, password },
{
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: typeof username === "string" ? username.toString() : "",
password: typeof password === "string" ? password.toString() : "",
}),
}
);
if (response.data.status) {
const tokenResponse = { accessToken: response?.data?.data?.token };
TokenService.setToken(tokenResponse);
} else {
console.log("false false false");
return response.data.status;
}
return response.data;
} catch (error) {
console.error(error);
}
}
);
import { createSlice } from "#reduxjs/toolkit";
import { AuthService } from "../../services/auth.service";
const userToken = localStorage.getItem("userToken")
? localStorage.getItem("userToken")
: null;
const initialState = {
userToken,
isLoading: false,
hasError: false,
userinfo: null,
};
const userSlice = createSlice({
name: "userSlice",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(AuthService.userLogin.pending, (state, action) => {
state.isLoading = true;
state.hasError = false;
})
.addCase(AuthService.userLogin.fulfilled, (state, action) => {
state.userToken = action.payload;
state.isLoading = false;
state.hasError = false;
})
.addCase(AuthService.userLogin.rejected, (state, action) => {
state.isLoading = false;
state.hasError = true;
});
},
});
export const selectUserToken = (state) => state.userSlice.userToken;
export const selectLoadingState = (state) => state.userSlice.isLoading;
export const selectErrorState = (state) => state.userSlice.hasError;
export default userSlice.reducer;
export default function SignIn() {
const navigate = useNavigate();
const error = useSelector(selectErrorState);
console.log(error);
const [username, setName] = useState("");
const [password, setPassword] = useState("");
const dispatch = useDispatch();
const data = {
username: username,
password: password,
};
const handleNameChange = (event) => {
setName(event.target.value);
};
const handlePasswordChange = (event) => {
setPassword(event.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(username, password);
const response = dispatch(AuthService.userLogin(data))
.unwrap()
.then(() => {
if (response.success) {
navigate("/");
}
});
console.log("RESPONSE", response);
console.log(error);
};
//index.js
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store={store}>
<BrowserRouter>
<Routes>
<Route path="login" element={<Login />} />
<Route path="register" element={<Register />} />
<Route path="" element={<Home />} />
<Route path="events" element={<Events />} />
</Routes>
</BrowserRouter>
</Provider>
</React.StrictMode>
);
log
my result from API:
Does it make more sense to use the status property here?
{data: {…}, status: true, exception: null, code: 200, message: null}
code: 200
data: {token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfb…yMzl9.1P25An_PHA9n4dyQ9JRKOjwPWWtQShWgW9In-gqS7Ek'}
exception: null
message: null
status: true
[[Prototype]]: Object
Even if I enter wrong information, the promise always seems to be fulfilled.
I think I need to use token in this process. The user will see his own information. I can get the token somehow, but I couldn't use it after I put it in localstorage.
While on the login page, it struggled with the data returned from the login meta, but I couldn't. I want to know what is the most logical method in such a situation. Thank you in advance for your answers.

Redirect to login page or landing page if session expire in react with react router 6

In my React App,I need to redirect the current page to landing page if session expires , I am using the
"react-router-dom": "^6.2.2",
My session expiration time is 60. I am doing the private routing with following approach,
export const getCurrentUser = () => async (dispatch) => {
try {
const res = await axios.get(BASE_URL + GET_CURRENT_USER_URL, { withCredentials: true });
localStorage.setItem('orgId', res.data.orgId);
dispatch({
type: GET_CURRENT_USER,
payload: res.data
});
return res.data;
} catch (err) {
console.log(err);
}
};
export default function PrivateOutlet() {
const [isLoaded, setIsLoaded] = useState(false);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getCurrentUser())
.then((res) => {
setIsLoaded(true);
localStorage.setItem('orgId', res.orgId);
})
.catch(error => console.log(error) );
}, [dispatch]);
return ( isLoaded ? (localStorage.getItem('orgId') !== undefined) ? <Outlet /> : <Navigate to="/landingPage" /> : <></>)
};
<Route path="/*" element={<PrivateOutlet />} >
<Route path="home" element={<Home />} />
</Route>
By using this approach, if I reload the page it is redirecting to landing page but ,If I not doing the reload and lets say I trigger any event it is showing nothing since Backend APIs are returning nothing since session has been expired.
So If session expired How I redirect the current page to landing page if I trigger any click or any event?
add state for error:
const [isError, setIsError] = useState(false);
set error state when result is failed:
.catch(error => setIsError(true) );
and before base return with isLoaded check add:
if (isError) {
return <Navigate to="/landingPage" replace={true} />
}

React useEffect() not working for Logout as expected

I am trying to implement a Login and Logout functionality.
Everything is working fine, but when I click Logout, I get a blank screen until I refresh the page instead of Login component.
So far, I have tried this:
function App() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [user, setUser] = useState()
const handleLogout = () => {
setUser({});
setUsername("");
setPassword("");
localStorage.clear();
};
const handleSubmit = async e => {
e.preventDefault();
const user = { username, password };
const response = await axios.post(
"http://<ip-address>/api-token-auth/",
user
);
setUser(response.data.token)
localStorage.setItem('user', response.data.token)
};
useEffect(() => {
const loggedInUser = localStorage.getItem("user");
if (loggedInUser) {
const foundUser = (loggedInUser);
setUser(foundUser);
}
}, []);
if (user) {
return (
<Router>
<Navigation
logout={handleLogout}
user = {user}
/>
<Switch>
<Route exact path="/" component={PermissionApply}/>
</Switch>
</Router>
)
}
return (
<Login
setUsername={setUsername}
setPassword={setPassword}
handleSubmit={handleSubmit}
/>
);
}
And my Navigation component:
<a className="nav-link float-right" onClick={props.logout}>Log Out</a>
Any help on why this is happening and how to fix it would be constructive.
Your useEffect is running once when the component is loaded because of the [].
If you watch for state changes on user, then it should work properly.
useEffect(() => {
const loggedInUser = localStorage.getItem("user");
if (loggedInUser) {
const foundUser = (loggedInUser);
setUser(foundUser);
}
}, [user]);
Also, in the handleLogout() you are setting the user state to an empty dictionary instead of null.
const handleLogout = () => {
setUser(); // This ensures that the user state is null
setUsername("");
setPassword("");
localStorage.clear();
};

State set with useState not passing down through props

I have a functional component that sets state with an onSubmit function. The problem is that the setState function won't update the user state in time for it to be passed down to the user prop before the authenticated state is changed to true. Here is the basic gist of my component:
import React, { useState } from 'react';
import axios from 'axios';
import { LoginForm } from './LoginForm';
import { LoggedIn } from './LoggedIn';
const { login } = require('../utils/login');
const { getUser } = require('../utils/getUser');
export const Splash = () => {
const [user, setUser] = useState(null);
const [authenticated, setAuthenticated] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const _handleEmail = (e) => {
setEmail(e.target.value);
};
const _handlePass = (e) => {
setPassword(e.target.value);
};
const _handleSubmit = async (e) => {
e.preventDefault();
const token = await login(email, password);
const fetchUser = await getUser(token);
setAuthenticated(true);
setUser(fetchUser);
console.log(fetchUser) <---- logs user fine
console.log(user) <----- logs empty object
};
const _handleLogout = async (e) => {
const logout = await
axios.get('http://localhost:5000/api/v1/auth/logout');
console.log(
`Logout status: ${logout.status}, Success: ${logout.data.success}`
);
setAuthenticated(false);
setUser({});
};
return (
<div>
{!authenticated ? (
<LoginForm
handleEmail={_handleEmail}
handlePass={_handlePass}
handleSubmit={_handleSubmit}
/>
) : (
<LoggedIn
user={user}
authenticated={authenticated}
logout={_handleLogout}
/>
)}
</div>
);
};
For whatever reason, my setAuthenticated function will change the authenticated state from false to true, hence triggering the render of <LoggedIn />, but my user state will not update in time to be passed down to <LoggedIn /> through the user prop. I suspect the authenticated prop I send down isn't updated either. Thoughts?
Thanks!
EDIT: Here's my <LoggedIn /> component and it's <LoggedInNav />component.
/// LoggedIn.js
export const LoggedIn = ({ logout, user, authenticated }) => {
LoggedIn.propTypes = {
logout: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
authenticated: PropTypes.bool.isRequired
};
const [loggedInUser, setUser] = useState({});
const [loggedInAuth, setAuthenticated] = useState(false);
useEffect(() => {
try {
if (user) {
setAuthenticated(true);
setUser(user);
}
} catch (err) {
console.log(err);
}
}, []);
return (
<div>
{!authenticated ? (
<Spinner animation='border' variant='primary' />
) : (
<LoggedinNav navUser={user} logoutButton={logout} />
)}
</div>
);
};
// LoggedInNav.js
export const LoggedinNav = ({ navUser, logoutButton }) => {
LoggedinNav.propTypes = {
navUser: PropTypes.object.isRequired,
logoutButton: PropTypes.func.isRequired
};
return (
<div>
<Navbar bg='light' variant='light'>
<Navbar.Brand href='#home'>
<img
src={logo}
width='120'
height='auto'
className='d-inline-block align-top ml-3'
alt='React Bootstrap logo'
/>
</Navbar.Brand>
<Nav className='mr-auto'>
<Nav.Link href='#feedback'>Submit Feedback</Nav.Link>
</Nav>
<Navbar.Collapse className='justify-content-end mr-4'>
<Navbar.Text>
Signed in as: {navUser.name.first} {navUser.name.last} - Role:{' '}
{navUser.role}
</Navbar.Text>
</Navbar.Collapse>
<Button onClick={logoutButton} variant='outline-primary mr-4'>
Logout
</Button>
</Navbar>
</div>
);
};
According to React Docs, useState, same as setState, triggers a new render.
Next render, the new value will be in user.
Because you are in an async method, youre out of the loop, and cannot see new state value in the next line.
See codesandbox poc:
In it the async function awaits 2 seconds and then set state.
You can see in the console that the render is triggered after the set state, the render sees the new state, but the async method prints the old one.
useState, same as setState, are syncronous, meaning they will directly trigger a render.
For async work, you can use useEffect

Categories