AuthStatus is not changing from configuring state using amplify ui react - javascript

I'm using AWS amplify UI react to create the authentication flow in the react application. I followed the document and made the navigation flow using the doc below.
https://ui.docs.amplify.aws/react/guides/auth-protected.
However, after logging in, I was able to see the login page flickering each time when I access any other route. To solve this, I followed one of the answers from the below question.
Flicker of login page on Authentication - useAuthenticator - React - Amplify Auth.
Unfortunately now, the page is always stuck in the "configuring" state and the authStatus never getting changed at all. How do I handle this scenario to automatically redirect to the login page if not authenticated and not show the login page each time user refreshes the page?
NOTE: This question is related to amplify-ui react with Authenticator.provider component.
RequireAuth.tsx - all routes are wrapped inside this
import { useLocation, Navigate } from "react-router-dom";
import { useAuthenticator } from "#aws-amplify/ui-react";
import PageLoader from "../../components/loader/page-loader";
export function RequireAuth({ children }: any) {
const location = useLocation();
const { authStatus, user } = useAuthenticator((context) => [
context.authStatus,
]);
console.log("authStatus:::", authStatus);
console.log("user:::", user);
if (authStatus === "authenticated") {
return <>{children}</>;
} else if (authStatus === "unauthenticated") {
return <Navigate to="/login" state={{ from: location }} replace />;
} else if (authStatus === "configuring") {
return <PageLoader />;
} else {
return <Navigate to="/login" state={{ from: location }} replace />;
}
}
And few routes in appRoutes.
<Route
path="/"
element={
<RequireAuth>
<AppLayout />
</RequireAuth>
}>
<Route
index
element={
<RequireAuth>
<Home />
</RequireAuth>
}
/>

Unfortunately, this appears to be a known bug with <Authenticator.Provider> and <Authenticator/>.
Until the bug is fixed, there is a known workaround that involves always including the <Authenticator/> component within the active dom structure and then hiding it using CSS. It's pretty terrible, but, worked for me:
[data-amplify-authenticator] {
display:none;
}
In the future, I am planning to write a custom UI and handle the authentication in my backend before dropping these components altogether.

Related

React useEffect useContext - context works with some components, but not in dynamic route

I am working on a React blog app and have posts in markdown files with metadata. The metadata is stored in Firestore. The .md files with the content are stored in Cloud Storage. The App component uses useEffect get the metadata for each post from Firestore and save it (an array of objects) into PostContext. This is the App component:
import React, { useEffect } from 'react'
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"
import { firestore } from './firebase'
import { usePostsUpdate } from "./contexts/PostContext"
import PrivateRoute from "./components/auth/PrivateRoute"
...
function App() {
const savePosts = usePostsUpdate()
useEffect(() => {
const postsRef = firestore.collection('metadata')
postsRef.get()
.then((snapshot) => {
const posts = snapshot.docs.map((doc) => ({
...doc.data()
}))
posts.sort((a, b) => b.postId - a.postId)
savePosts(posts)
console.log('App: in useEffect, posts.length', posts.length)
})
}, [])
console.log('APP')
return (
<React.Fragment>
<Router>
<Switch>
<Route exact path="/" component={Home} />
...
<Route path="/all-posts" component={AllPostsPage} />
<Route path='/post/:id' render={props => <Post {...props} />} />
<Route path='/404' component={NotFoundPage} />
</Switch>
</Router>
</React.Fragment>
);
}
export default App;
There are 2 issues. First, regarding context. The 'savePost()' function is saving the array to PostContext, which is being used in the home and 'all-posts' pages successfully. But, when I try a '/post/:id' to access a specific post, the array pulled in from PostContext is empty. Somehow, it seems that the array is being reset to it's default (empty) value, so there are errors when I try to access it (the array holds the urls to access the markdown files in Cloud Storage that are needed to retrieve the content needed on the page). I can't find a reason for this to happen.
Second, React is telling me to include 'savePosts' as a dependency in useEffect. But when I do that, it just loops. The same thing happens if I remove the dependency array (which I don't really want to do). There is no dependency I can add that satisfies React and doesn't loop continuously.
Now it works. I was entering urls manually to test the routing. I went ahead and set up dynamic routing (routing to /post/id by clicking on a post summary on the home page), and now the array in context is populated properly. I don't understand why manually entering 'localhost:3000/post/1' in the browser works differently than routing to the same URL by clicking on a link, but it does.
That didn't solve the issue with the React useEffect warning, but I can live with that.

React Router re-render route instead of re-render component

I'm working on an app that is using React Router and I noticed that when my Redux store changes state that the router is re-rendering the component the current route refers to rather than re-rendering the route itself.
To illustrate the problem; I have implemented a PrivateRoute that checks if a user is currently logged in. In it's most basic form it looks something like this:
const PrivateRoute = ({component: Component, ...rest}) => {
return <Route {...rest} render={(props) => {
const state = store.getState()
if (state.isAuthenticated) {
return <Component {...props}/>
}
else {
return <Redirect to={{pathname: '/login'}}/
}
}}/>
})
This works great since I can now say something like this:
<PrivateRoute path="/" component={HomePage}/>
However, I noticed that when the state of isAuthenticated changes that React Router is calling the render method on the HomePage component rather than that it re-renders the route. This means that the application will only do the authentication check when a user goes from some page to the home page but once on the home page, the check is no longer performed.
The only work around I have at the moment is to move the authentication check into the render function of the component (which is obviously not where it belongs).
How can I make React Router re-render the route rather than re-render the component the route refers to when the state changes?
I managed to solve the problem by using a Higher Order Component rather than implementing the authentication check in the route.
function withEnsureAuthentication(WrappedComponent) {
return class extends React.Component {
render() {
if (this.props.store.isAuthenticated === false) {
return <Redirect to={{pathname: '/login'}}/>
}
return <WrappedComponent {...this.props}/>
}
}
}
You can now use a normal Route but apply the withEnsureAuthentication to the component:
const HomePage = withEnsureAuthentication(Home)
<Route path="/home" component={HomePage}/>

React Router V4 protected private route with Redux-persist and React-snapshot

I'm implementing private route like so using React Router Route Component:
function PrivateRoute({component: Component, authed, emailVerified, ...rest}) {
return (
<Route
{...rest}
render={props =>
authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/', state: {from: props.location}}} />}/>
)
}
Expected Behavior:
authed is persisted through page refresh through using redux-persist
So on page refresh or reload, if authed prop is true then router should render <Component /> without ever going to path "/"
Actual Behavior which is the Problem:
With authed === true (persisted)
reloading the page or refreshing it leads to the following actions taking place(checked redux devtools)
the action:
"##router/LOCATION_CHANGE" runs and takes it to the correct secure route but then "##router/LOCATION_CHANGE" runs again and it redirects to "/" for a moment and finally
"##router/LOCATION_CHANGE" runs again and directs route back to the secure path, even though authed === true through all this in the redux devtools
Then: My guess was that this error has something to with my main App Component rendering before redux-persist has time to re-hydrate the Redux store.
So I tried doing the following:
I tried delaying my main App component render until my store is re-hydrated using redux-persist like so:
render() {
const {authed, authedId, location, rehydrationComplete} = this.props
return (
<div>
{ rehydrationComplete
? <MainContainer>
<Switch key={location.key} location={location}>
<Route exact={true} path='/' component={HomeContainer} />
<Route render={() => <h2> Oops. Page not found. </h2>} />
</Switch>
</MainContainer>
: <div>...Loading </div> }
</div>
)
}
This effectively fixes the issue above of the path changing when "##router/LOCATION_CHANGE" action runs(only Changes the path keys), However this leads to another Issue with React-snapshot Now: all the static generated html files from react-snapshot Now contain only ...Loading. I tried to set snapshotDelay of 8200 in the react-snapshot options but that didnt solve the issue.
Then:
I tried the following to delay React-snapshot call so that it renders html after the store has been rehydrated:
import {render as snapshotRender} from 'react-snapshot'
import {ConnectedRouter} from 'react-router-redux'
async function init() {
const store = await configureStore()
snapshotRender(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('root')
)
registerServiceWorker()
}
init()
But now i get the error: that 'render' from react-snapshot was never called. Did you replace the call to ReactDOM.render()?
I know this is a loaded question, but I want to effectively use these 3 libs(React-Router V4, Redux-persist, React-snapshot) together to serve protected routes without the mentioned errors.
I have something similar to you. Here I use React-Router V4 and a persist-like library.
Your router/routes doesn't need to be aware of the persist. They should rely on your redux's store. The persist should rehydrate your store with all the data.
I didn't see where you are using the PrivateRoute component in your example. Where is it?

React Router 4 authentification and redirection

I'm trying to implement an authentification system to my app with React Router.
But i'm having trouble to find a correct way to do this.
export default class App extends React.Component {
constructor(props){
super(props);
this.state = {
logged : false
}
}
isUserConnected(){
// Contact server to know if user is connected or not
}
render() {
return (
<Router>
<Switch>
<Route exact path="/" component={Homepage} />
<Route path="/privatePart" component={Application} />
<Route path="/login" component={LoginRedirection} />
<Route component={NotFound} />
</Switch>
</Router>
)
}
}
In the LoginRedirection component, there is a login form that make a POST to connect the user. But how this component can set logged to true in the App component ?
Maybe I should learn redux ? It may be simplier to implement authentification with it ?
Redux could be a solution, but you can do this with React and react-router.
Using react-router, you can use the property onEnter. For example, you can make the privatePart URL accessible only to logged users with something like:
<Route path="/privatePart" component={Application} onEnter={requireAuth}/>
requireAuth should be a function that checks if the user has logged in or not. I think that using App's state isn't the best way to do this, since that information will be lost if the web is reloaded. You could store the login token or just a flag in the localStorage when the POST returns OK in the Login component. Then, redirect the user to privatePart and he will only be able to access it if requireAuth returns true (checking the localStorage). It could be something like:
if (localStorage.getItem("logged") === null) {
return false;
} else {
return true;
}
where username is the key you would add to the localStorage when the login is successful.

React-Router External link

Since I'm using React Router to handle my routes in a React app, I'm curious if there is a way to redirect to an external resource.
Say someone hits:
example.com/privacy-policy
I would like it to redirect to:
example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies
I'm finding exactly zero help in avoiding writing it in plain JavaScript at my index.html loading with something like:
if (window.location.path === "privacy-policy"){
window.location = "example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies"
}
Here's a one-liner for using React Router to redirect to an external link:
<Route path='/privacy-policy' component={() => {
window.location.href = 'https://example.com/1234';
return null;
}}/>
It uses the React pure component concept to reduce the component's code to a single function that, instead of rendering anything, redirects browser to an external URL.
It works both on React Router 3 and 4.
With Link component of react-router you can do that. In the "to" prop you can specify 3 types of data:
a string: A string representation of the Link location, created by concatenating the location’s pathname, search, and hash properties.
an object: An object that can have any of the following properties:
pathname: A string representing the path to link to.
search: A string representation of query parameters.
hash: A hash to put in the URL, e.g. #a-hash.
state: State to persist to the location.
a function: A function to which current location is passed as an argument and which should return location representation as a string or as an object
For your example (external link):
https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies
You can do the following:
<Link to={{ pathname: "https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies" }} target="_blank" />
You can also pass props you’d like to be on the such as a title, id, className, etc.
There isn’t any need to use the <Link /> component from React Router.
If you want to go to external link use an anchor tag.
<a target="_blank" href="https://meetflo.zendesk.com/hc/en-us/articles/230425728-Privacy-Policies">Policies</a>
It doesn't need to request React Router. This action can be done natively and it is provided by the browser.
Just use window.location.
With React Hooks
const RedirectPage = () => {
React.useEffect(() => {
window.location.replace('https://www.google.com')
}, [])
}
With React Class Component
class RedirectPage extends React.Component {
componentDidMount(){
window.location.replace('https://www.google.com')
}
}
Also, if you want to open it in a new tab:
window.open('https://www.google.com', '_blank');
I actually ended up building my own Component, <Redirect>.
It takes information from the react-router element, so I can keep it in my routes. Such as:
<Route
path="/privacy-policy"
component={ Redirect }
loc="https://meetflo.zendesk.com/hc/en-us/articles/230425728-Privacy-Policies"
/>
Here is my component in case anyone is curious:
import React, { Component } from "react";
export class Redirect extends Component {
constructor( props ){
super();
this.state = { ...props };
}
componentWillMount(){
window.location = this.state.route.loc;
}
render(){
return (<section>Redirecting...</section>);
}
}
export default Redirect;
Note: This is with react-router: 3.0.5, it is not so simple in 4.x
I went through the same issue. I want my portfolio to redirect to social media handles. Earlier I used {Link} from "react-router-dom". That was redirecting to the sub directory as here,
Link can be used for routing web pages within a website. If we want to redirect to an external link then we should use an anchor tag. Like this,
Using some of the information here, I came up with the following component which you can use within your route declarations. It's compatible with React Router v4.
It's using TypeScript, but it should be fairly straightforward to convert to native JavaScript:
interface Props {
exact?: boolean;
link: string;
path: string;
sensitive?: boolean;
strict?: boolean;
}
const ExternalRedirect: React.FC<Props> = (props: Props) => {
const { link, ...routeProps } = props;
return (
<Route
{...routeProps}
render={() => {
window.location.replace(props.link);
return null;
}}
/>
);
};
And use with:
<ExternalRedirect
exact={true}
path={'/privacy-policy'}
link={'https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies'}
/>
The simplest solution is to use a render function and change the window.location.
<Route path="/goToGoogle"
render={() => window.location = "https://www.google.com"} />
If you want a small reusable component, you can just extract it like this:
const ExternalRedirect = ({ to, ...routeProps }) => {
return <Route {...routeProps} render={() => window.location = to} />;
};
and then use it (e.g. in your router switch) like this:
<Switch>
...
<ExternalRedirect exact path="/goToGoogle" to="https://www.google.com" />
</Switch>
I had luck with this:
<Route
path="/example"
component={() => {
global.window && (global.window.location.href = 'https://example.com');
return null;
}}
/>
I solved this on my own (in my web application) by adding an anchor tag and not using anything from React Router, just a plain anchor tag with a link as you can see in the picture screenshot of using anchor tag in a React app without using React Router
Basically, you are not routing your user to another page inside your app, so you must not use the internal router, but use a normal anchor.
Although this is for a non-react-native solution, but you can try.
In React Router v6, component is unavailable. Instead, now it supports element. Make a component redirecting to the external site and add it as shown.
import * as React from 'react';
import { Routes, Route } from "react-router-dom";
function App() {
return(
<Routes>
// Redirect
<Route path="/external-link" element={<External />} />
</Routes>
);
}
function External() {
window.location.href = 'https://google.com';
return null;
}
export default App;
In React Route V6 render props were removed. It should be a redirect component.
RedirectUrl:
const RedirectUrl = ({ url }) => {
useEffect(() => {
window.location.href = url;
}, [url]);
return <h5>Redirecting...</h5>;
};
Route:
<Routes>
<Route path="/redirect" element={<RedirectUrl url="https://google.com" />} />
</Routes>
I think the best solution is to just use a plain old <a> tag. Everything else seems convoluted. React Router is designed for navigation within single page applications, so using it for anything else doesn't make a whole lot of sense. Making an entire component for something that is already built into the <a> tag seems... silly?
To expand on Alan's answer, you can create a <Route/> that redirects all <Link/>'s with "to" attributes containing 'http:' or 'https:' to the correct external resource.
Below is a working example of this which can be placed directly into your <Router>.
<Route path={['/http:', '/https:']} component={props => {
window.location.replace(props.location.pathname.substr(1)) // substr(1) removes the preceding '/'
return null
}}/>
I don't think React Router provides this support. The documentation mentions
A < Redirect > sets up a redirect to another route in your application to maintain old URLs.
You could try using something like React-Redirect instead.
I was facing the same issue and solved it using by http:// or https:// in React.
Like as:
<a target="_blank" href="http://www.example.com/" title="example">See detail</a>
You can use for your dynamic URL:
<Link to={{pathname:`${link}`}}>View</Link>
For V3, although it may work for V4. Going off of Eric's answer, I needed to do a little more, like handle local development where 'http' is not present on the URL. I'm also redirecting to another application on the same server.
Added to the router file:
import RedirectOnServer from './components/RedirectOnServer';
<Route path="/somelocalpath"
component={RedirectOnServer}
target="/someexternaltargetstring like cnn.com"
/>
And the Component:
import React, { Component } from "react";
export class RedirectOnServer extends Component {
constructor(props) {
super();
// If the prefix is http or https, we add nothing
let prefix = window.location.host.startsWith("http") ? "" : "http://";
// Using host here, as I'm redirecting to another location on the same host
this.target = prefix + window.location.host + props.route.target;
}
componentDidMount() {
window.location.replace(this.target);
}
render(){
return (
<div>
<br />
<span>Redirecting to {this.target}</span>
</div>
);
}
}
export default RedirectOnServer;
I am offering an answer relevant to React Router v6 to handle dynamic routing.
I created a generic component called redirect:
export default function Redirect(params) {
window.location.replace('<Destination URL>' + "/." params.destination);
return (
<div />
)
}
I then called it in my router file:
<Route path='/wheretogo' element={<Redirect destination="wheretogo"/>}/>
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
function App() {
return (
<Router>
<Route path="/" exact>
{window.location.replace("http://agrosys.in")}
</Route>
</Router>
);
}
export default App;
Using React with TypeScript, you get an error as the function must return a React element, not void. So I did it this way using the Route render method (and using React router v4):
redirectToHomePage = (): null => {
window.location.reload();
return null;
};
<Route exact path={'/'} render={this.redirectToHomePage} />
Where you could instead also use window.location.assign(), window.location.replace(), etc.
Complementing Víctor Daniel's answer here: Link's pathname will actually take you to an external link only when there's the 'https://' or 'http://' before the link.
You can do the following:
<Link to={{ pathname:
> "https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies"
> }} target="_blank" />
Or if your URL doesn't come with 'https://', I'd do something like:
<Link to={{pathname:`https://${link}`}} target="_blank" />
Otherwise it will prepend the current base path, as Lorenzo Demattécommented.
If you are using server-side rending, you can use StaticRouter. With your context as props and then adding <Redirect path="/somewhere" /> component in your app. The idea is every time React Router matches a redirect component it will add something into the context you passed into the static router to let you know your path matches a redirect component.
Now that you know you hit a redirect you just need to check if that’s the redirect you are looking for. then just redirect through the server. ctx.redirect('https://example/com').
You can now link to an external site using React Link by providing an object to to with the pathname key:
<Link to={ { pathname: '//example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies' } } >
If you find that you need to use JavaScript to generate the link in a callback, you can use window.location.replace() or window.location.assign().
Over using window.location.replace(), as other good answers suggest, try using window.location.assign().
window.location.replace() will replace the location history without preserving the current page.
window.location.assign() will transition to the URL specified, but will save the previous page in the browser history, allowing proper back-button functionality.
location.replace()
location.assign()
Also, if you are using a window.location = url method as mentioned in other answers, I highly suggest switching to window.location.href = url.
There is a heavy argument about it, where many users seem to adamantly want to revert the newer object type window.location to its original implementation as string merely because they can (and they egregiously attack anyone who says otherwise), but you could theoretically interrupt other library functionality accessing the window.location object.
Check out this conversation. It's terrible.
JavaScript: Setting location.href versus location
I was able to achieve a redirect in react-router-dom using the following
<Route exact path="/" component={() => <Redirect to={{ pathname: '/YourRoute' }} />} />
For my case, I was looking for a way to redirect users whenever they visit the root URL http://myapp.com to somewhere else within the app http://myapp.com/newplace. so the above helped.

Categories

Resources