How to render a component just after an async method? - javascript

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.

Related

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;

React: What is the best way on implementing a server sided SSO login?

I'm creating a simple sso login react project with python as my backend server and reactJS as my frontend. I'm using react-google-login as my SSO login button. I've already setup my backend server which receives information from the google api and verifies the Token ID from it. My react app's logged in state will depend on an api call to the backend which verifies the posted Token ID, this runs everytime the app is rendered.
My problem is that everytime I render my app when logged in state is true, my login page shows for a brief second then continues on the home page. I've realized that it is because i'm using useState for the variable that defines if the logged in state of my app is true or false.
Here's my App.js
function App() {
return (
<div id="routes">
<Routes>
<Route exact path="/login" element={<Login/>}/>
<Route element={<ProtectedRoutes />}>
<Route path="/" element={<SearchBar/>}/>
<Route path="/searched" element={<Table/>}/>
<Route path="/tablecomponent" element={<AntdTable/>}/>
</Route>
</Routes>
</div>
);
}
export default App;
ProtectedRoutes.js
const useAuth = () => {
const [users, setUsers] = useState(null)
useEffect(() => {
(async () => {
try {
const resp = await axios.get("/#me");
setUsers(resp.data);
} catch (error) {
console.log("Not authenticated");
}
})();
}, []);
const user = { loggedIn: users!=null };
return user && user.loggedIn;
};
const ProtectedRoutes = () => {
const isAuth = useAuth();
return isAuth ? <Outlet /> : <Login/>;
};
export default ProtectedRoutes;
Login.js
export default function Login() {
let navigate = useNavigate()
const [tokenID, setTokenID] = useState(null)
const clientId = "client_id"
const onLoginSuccess = (res) =>{
console.log("Login Success:", res);
setTimeout(()=>{
window.location.reload()
},1000)
try{fetch('/verifytoken',{
method: 'POST',
body: JSON.stringify({
token: res.tokenId,
email: res.profileObj.email,
}), headers:{"Content-type":"application/json; charset=UTF-8"}
}).then(response => response.json()).then(message=>console.log(message))
}
catch(error){
alert(error)
}
}
const onLoginFailure = (res)=>{
console.log("Login Failed:", res)
}
return (
<div>
Login Page <br/>
<Link to="/">
<button>button</button>
</Link>
<GoogleLogin
clientId={clientId}
buttonText="Login"
onSuccess={onLoginSuccess}
onFailure={onLoginFailure}
cookiePolicy={"single_host_origin"}
/>
</div>
)
}

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 do I solve the problem of Promise pending

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/" />
}
/>)
}
}

can't perform a react state update on an unmounted component issue with useEffect

I'm trying to redirect my user to a private route. I'm using redux thunk to fetch user info from the database, with storeUser(), if the info exists then the user proceeds otherwise they get redirected back to the home page. However its not working as expected. Its redirecting back to the home page when It should be proceeding. I can do this using class based syntax and componentDidMount. I tried to counter this issue of no access to componentDidMount by using the authChecked state to determine when the component has finished rendering
const PrivateRoute = (props) => {
const [authChecked, handleAuthChecked] = useState(false);
const [isAuth, handleIsAuth] = useState(false);
useEffect(() => {
props
.storeUser()
.then(() => {
props.user.email ? handleIsAuth(true) : handleIsAuth(false);
handleAuthChecked(true);
})
.catch(() => {
handleAuthChecked(true);
});
}, [props]);
if (authChecked) {
return isAuth ? <props.component /> : <Redirect to="/" />;
}
return null;
};
const mapStateToProps = (state) => {
return {
user: state.user,
};
};
export default connect(mapStateToProps, { storeUser })(PrivateRoute);
The code will always redirect the user though. isAuth will never return true even though props.user.email is true. It runs and redirects before it has chance to run handleIsAuth(true)
You have 2 issues that may be causing the defects you see:
First issue is caused by function scope within useEffect and your callback for storeUser. Instead of relying on the callback to determine whether the user has an email address, just do that in your render condition and let redux + react render cycle help you out.
In addition, you should only call the storeUser action on mount. Not every time props updates.
For example:
const PrivateRoute = (props) => {
const [authChecked, handleAuthChecked] = useState(false);
useEffect(() => {
props
.storeUser()
.then(() => {
handleAuthChecked(true);
})
.catch(() => {
handleAuthChecked(true);
});
}, []);
if (authChecked) {
return !!props.user.email
? <props.component />
: <Redirect to="/" />;
}
return null;
};
const mapStateToProps = (state) => {
return {
user: state.user,
};
};

Categories

Resources