How do I solve the problem of Promise pending - javascript

I am currently trying to make a protected route function in react. I wish that result will return a boolean value rather than a promise without changing ProtectedRoute to async:
import React from "react";
import { Redirect, Route } from "react-router-dom";
import { CheckToken } from "./RequestAction"
function ProtectedRoute({ component: Component, ...restOfProps }) {
const result = (async () => {
const res = await CheckToken()
return res;
})()
console.log(result); //log-> PromiseĀ {<pending>}[[Prototype]]: Promise[[PromiseState]]: "fulfilled"[[PromiseResult]]: false
return (
<Route
{...restOfProps}
render={(props) =>
result ? <Component {...props} /> : <Redirect to="/login/" />
}
/>
);
}
export default ProtectedRoute;
This is the CheckToken function:
import axios from "axios";
async function CheckToken() {
let result = null;
await axios
.get(`http://localhost:5000/protected`,
{"withCredentials": true}
)
.then((res) => {
console.log("res.data.status:",res.data)
if (res.data.status === "success") {
result = true
}
})
.catch((err) => {
result = false
});
console.log("result:",result);
return result
}

const useToken = () => {
const [token, setToken] = useState(null);
useEffect(() => {
const f = async () => {
const res = await CheckToken();
//TODO: add check login;
setToken(res);
};
f();
})
return token;
}
function ProtectedRoute({ component: Component, ...restOfProps }) {
const token = useToken();
return (
<Route
{...restOfProps}
render={(props) =>
token ? <Component {...props} /> : <Redirect to="/login/" />
}
/>
);
}
Function component body cannot be async.

OK, so in your FunctionComponent you are trying to contact a server on a per render basis, and you are saying you want to capture the result in a boolean, that is then passed onto your child Route component
Firstly, network requests are considered effects, usually triggered by a change in your component props. you would want to handle the network behavior asynchronously as it would block the rendering of the component otherwise, which is something React doesn't allow. Also, you probably only want to check the token in specific circumstances, depending on your props.
You can still render your Route child component once your network request has resolved, but until then, there will be an intermediary state that your component will need to handle. How it does that is up to you, but you should probably not render a link until you have that result information.
I'd introduce a bit of state in a custom hook, and just have your component return out some intermediary state until you get a result / error.
I would also just return a token, you can use null to indicate the network request has not completed, and empty string if one hasn't been retrieved.
const useToken = () => {
const [state, setState] = useState(null)
useEffect(() => {
//TODO: you would also want to catch errors
CheckToken().then(setState)
})
return state
}
function ProtectedRoute({ component: Component, ...restOfProps }) {
const token = useToken()
if (token === null) {
return <Spinner/> // something that indicates you are waiting
}
return (
<Route
{...restOfProps}
render={(props) =>
token ? <Component {...props} /> : <Redirect to="/login/" />
}
/>)
}
}

Related

How can I call a function and return a React Component at the same time?

I have this React Component for Privates routes. I want to check if user is logged in, so I check if jwt is in local storage and if token is not expired. If this doesn't happen I want to log user out, since token is expired.
I have a react warning that says I can't return a function and Component at the same level.
What would be the best approach to achieve this?
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import dayjs from 'dayjs';
import { useUserStore } from '#store/userStore';
import Main from '#layouts/Main';
function PrivateRoutes() {
const { expiresIn, removeUser } = useUserStore();
const now = dayjs();
const handleRemoveUser = () => {
removeUser();
};
return window.localStorage.getItem('jwt') && dayjs(expiresIn).isAfter(now) ? (
<Main>
<Outlet />
</Main>
) : (
<>
{handleRemoveUser}
<Navigate to='/login' />
</>
);
}
export default PrivateRoutes;
I have tried using handler functions, arrow functions but not sure how to approach this situation
The render function of a React component is to be considered a pure function, there should be no unintentional side-effects during the render such as calling functions to de-authenticate users. For this you'll need to use the useEffect hook to issue an intentional side-effect as part of the normal React component lifecycle.
Compute the authentication condition and use as a dependency for a useEffect hook that conditionally calls removeUser, while at the same time conditionally returning valid JSX in the function return.
Example:
function PrivateRoutes() {
const { expiresIn, removeUser } = useUserStore();
const now = dayjs();
const jwt = window.localStorage.getItem('jwt');
const isNotExpired = dayjs(expiresIn).isAfter(now);
const isAuth = jwt && isNotExpired;
React.useEffect(() => {
if (!isAuth) {
removeUser();
}
}, [isAuth, removeUser]);
return isAuth
? (
<Main>
<Outlet />
</Main>
)
: <Navigate to="/login" replace />;
}
There is another way of doing that by Using HOC
const AuthCheck = (WrappedComponent: ComponentType) =>
function decorator(props) {
const [mount, setMount] = useState(false);
const { expiresIn, removeUser } = useUserStore();
const now = dayjs();
const jwt = window.localStorage.getItem('jwt');
const isNotExpired = dayjs(expiresIn).isAfter(now);
const isAuth = jwt && isNotExpired;
useEffect(() => {
setMount(true);
}, []);
useEffect(() => {
if (!isAuth) {
removeUser();
}
}, [isAuth, removeUser]);
return mount ? (
isAuth ? (
<WrappedComponent {...props} />
) : (
<>
Unathorised login <Navigate to='/login' replace />
</>
)
) : null;
};
export default AuthCheck;
need wrapped protected main container component with AuthCheck HOC
eg:
const Dashboard = () => {
return <></>
}
export default AuthCheck(Dashboard);

Async Route Element Return in React JS

Getting Error: Guard(...): Nothing was returned from render. This usually means a return statement is missing.
I want to call the API in my guard component before returning the element to Route Element to check if the logged in user have access to specific route or not.
For that, I have implemented a guard. Here's the route file code.
export default function Router() {
return (
<Routes>
<Route path="organizations">
<Route path="list" element={<Guard><ListOrganization /></Guard>} />
</Routes >
);
}
Guard component code
const Guard = (props) => {
fetch('https://apiToCheckPermission.com')
.then(response => {
if (response.isPermission) {
return props.children;
}
})
};
export default Guard;
It's kind of a thing that, I want to implement async route element. But React is throwing error if I don't immediately return the element from Guard.
Can anyone please tell How to solve this error?
If Guard is supposed to be a React component then it needs to always return valid JSX. The Guard currently returns nothing. I.E. it would need to return something from the Promise chain and then return that from the function body.
To resolve use some local state to hold a confirmed/verified permission value and conditionally render the children prop or a fallback. A typical route protection implementation will wait to confirm a user's access then render either the children or redirect to the login page with the location being accessed so the user can be redirected back after authenticating.
Example:
const Guard = ({ children }) => {
const location = useLocation();
const [hasPermission, setHasPermission] = React.useState(); // <-- initially undefined
React.useEffect(() => {
);
fetch('https://apiToCheckPermission.com')
.then(response => {
setHasPermission(response.isPermission);
});
}, []);
if (hasPermission === undefined) {
return null; // or loading indicator, spinner, etc
}
return hasPermission
? children
: <Navigate to="/login" replace state={{ from: location }} />;
};
Try to define a state to handle the permission and useEffect to load the data:
const Guard = (props) => {
const [hasPermission, setHasPermission] = useState(false);
useEffect(() => {
const fetchPermission = async () => {
const response = await fetch('https://apiToCheckPermission.com');
setHasPermission(response.isPermission);
}
fetchPermission().catch(console.error);
}, []);
if (!hasPermission) return <>Unauthorized</>;
return props.children;
};
export default Guard;

Child props is null when loading component from parent

I'm very new to React - so bear with me.
I'm trying to create a set of authentication protected routes/components. I have the below code that I am using to achieve that.
However, my issue is that when the child component loads, the userInfo is {} (i.e. not set). I know that the userInfo is being returned from the userService as my console.log returns the correct data.
Am I going about this right? I want to be able to protect a component/route, and pass through the userInfo to any protected route so I can do stuff with the data in the respective component.
const UserAuthenticatedRoute = ({component: Component, ...rest}) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [userInfo, setUserInfo] = useState({});
useEffect(async () => {
const r = await userService.isUserLoggedIn();
console.log(r.status);
if (r.status === 200){
setIsLoggedIn(true);
const userInfo = await r.json();
console.log(userInfo);
setUserInfo(userInfo);
} else {
setIsLoggedIn(false);
}
}, []);
return (
<Route {...rest} render={props => (
<>
<main>
{isLoggedIn &&
<Component {...props} userInfo={userInfo}/>
}
</main>
</>
)}
/>
);
};

How to render a component just after an async method?

I have a custom route that renders a page or redirects the user to the login page based on if the user logged in or not.
const AuthenticatedRoute = ({ children, ...rest }) => {
const auth = useContext(AuthContext);
const [isAuthenticated, setIsAuthenticated] = useState(null);
useEffect(() => {
const getAuth = async () => {
const res = await auth.isAuthenticated();
setIsAuthenticated(() => res);
};
getAuth()
}, [auth]);
return (
<Route
{...rest}
render={() => {
return isAuthenticated ? (
<>{children}</>
) : (
<Redirect to="/login" />
);
}}
></Route>
);
};
As you see inside useEffect I run an async method. The problem is that whenever the component wants to mount, the default value of isAuthenticated will be used and redirects the user to the login page. I'm a little confused about how to handle this situation. I don't want the component to be rendered when the async method is not completely run.
i believe it will process all your code before sending html to client's browser.

useState is resetting to default after initial and every subsequent render in ReactJS

the below code is not returning any errors but is not working either as per the logic.
So below are two components, APP() as parent component and Signin() as child component, in parent component i am use useEffect and whenever there is render happens it first check the server with cookies if valid then it will update the siginalready= true(initial default value is false) and then will pass the state to the child component, but when next time i refresh the page it takes as siginalready=false as initial value (which is odd as it should be true already after first time).
This behaviour of resetting the state value of siginalready is effecting the project as when second time (i click account information it fist shows me the loginpage(siginalready =false) and then in less than a second it shows me the account info page((siginalready =true).
i got a feeling that it is something i am missing with useEffect function, but i dont see any thing wrong in it. any suggestions guys.
this is the snippet from Parent component.
import Signin from "./Component/Signin";
function App() {
const [siginalready, setifsignedin] = useState(false);
const [userinfonew, setUserinfo] = useState([]);
let url = "http://localhost:5000/api/verifyifloginalready";
let options = {
credentials: "include",
method: "POST",
};
let verifyifloginalready = new Request(url, options);
useEffect(() => {
credentailverify();
}, []);
function credentailverify() {
(async () => {
const x1 = await fetch(verifyifloginalready)
.then((res) => {
if (res.status == 400 || res.status == 401) {
console.log(res.status);
// to do call delete current cookies function
return setifsignedin(false);
} else if (siginalready == false) {
setifsignedin(true);
return res.json();
} else {
return;
}
})
.then((data) => {
setUserinfo(data.data);
})
.catch((err) => console.log("err"));
return x1;
})();
}
return (
<Router>
<div className="App">
<header className="header">
<Nav userinfo={userinfonew} userstatus={siginalready} />
</header>
<div className="main">
<Sidebar />
<Switch>
<Route
path="/"
exact
render={(props) => <Home {...props} userinfo={userinfonew} />}
/>
from router {/* render={props=>(<newComponent}/> )} */}
/>
<Route
path="/signin"
exact
render={(props) => (
<Signin
{...props}
userinfo={userinfonew}
userstatus={siginalready}
/>
)}
/>
</Switch>
</div>
<div className="footer">
<Footer />
</div>
</div>
</Router>
);
}
const Home = () => (
<div>
<h1> Home page</h1>
</div>
);
export default App;
The below is the snippet of code from Child component
function Signin({ userinfo, userstatus }) {
return (
<div>
{userstatus ? (
<Useraccount newuserinfo={userinfo} updateduserstatus={userstatus} />
) : (
<SigninOptions />
)}
</div>
);
}
export default Signin;
but when next time i refresh the page it takes as siginalready=false as initial value (which is odd as it should be true already after first time)
This is what you've done:
const [siginalready, setifsignedin] = useState(false);
siginalready get false as a default value. credentailverify is an async function, so it takes time for it to call setifsignedin(true).
That's why SigininOptions component is rendered at the first time.
You can save login status after successful login to fix this:
// Put this function outside of App component
const isLoggedIn = () => {
// user info can be loaded after refresh
return !!window.localStorage.getItem('user-info'); // !! : cast to boolean
}
// And then inside App component
const [siginalready, setifsignedin] = useState(isLoggedIn());
function credentailverify() {
(async () => {
const x1 = await fetch(verifyifloginalready)
.then((res) => {
if (res.status == 400 || res.status == 401) {
console.log(res.status);
// to do call delete current cookies function
window.localStorage.removeItem('user-info');
return setifsignedin(false);
} else if (siginalready == false) {
setifsignedin(true);
return res.json();
} else {
return;
}
})
.then((data) => {
setUserinfo(data.data);
// save user login status
window.localStorage.setItem('user-info', JSON.stringify(data.data));
})
.catch((err) => console.log("err"));
return x1;
})();
}
IMPORTANT: You might want to handle authentication with JWT for SPA including react in a real world program.

Categories

Resources