I am following react-router docs to create a protected route HOC and for unauthenticated requests I am Redirecting the user as following :
<Redirect to={{
pathname: '/input-number',
state: { from: props.location },
}} />
Now on the redirected component, I show an error message using the following logic :
this.props.location.state && this.props.location.state.from &&
(
<Grid.Row>
<Grid.Column>
<Message negative>
<Message.Header>You must be logged in to visit the page</Message.Header>
</Message>
</Grid.Column>
</Grid.Row>
)
The problem is when the user reloads, the page the state associated with the location is not cleared and the error message is shown on each refresh once user has been redirected to the login component. I am looking for a way to clear the state. I think it must be possible without setting some app state.
UPDATE : I am adding the complete PrivateRoute for clarity:
`
export default function PrivateRoute({ component: Component, ...rest }) {
const isAuthenticated = localStorage.getItem(ACCESS_TOKEN);
return (
<Route
{...rest}
render={props => (
isAuthenticated ? (
<Component {...props} />
) : (
<Redirect to={{
pathname: '/input-number',
state: { from: props.location },
}}
/>
)
)}
/>);
}
`
When creating the history object for your <Router> during the initial setup of the React app, you could replace the state on the history's location with a state that does not include the from prop.
const history = createHistory();
if (history.location && history.location.state && history.location.state.from) {
const state = { ...history.location.state };
delete state.from;
history.replace({ ...history.location, state });
}
Also, it is possible to replace the state without merging location it statys untouched:
const { state } = this.props.location;
if (state && state.copySurveyId) {
this.duplicateSurvey(state.copySurveyId);
const stateCopy = { ...state };
delete stateCopy.copySurveyId;
this.props.history.replace({ state: stateCopy });
}
This might not be the most ideal approach but in my case the easiest way to deal with this was to use history.replace() and replace the current URL with the same URL but omitting location location state object (should work for params too). However you have to be careful and only replace the URL AFTER you have consumed the value of location state object or params. In my case:
// Somwhere in the app:
history.push("to/app/route", {
contentOfLocationState: true
});
// In the component that is mapped to above route
function (){
try {
// Post to API
Post(...{ contentOfLocationState });
} catch (e) {
// error
} finally {
history.replace("to/app/route");
}
PS: Using the technique that #robert-farley described react router would start acting up and show white screen for me. Might have been something wrong with my setup though
Related
I am using the useAuth hook to my authContext, so that I can use hook to set global states for my components.
Now, I am trying to navigate to the '/' page if the user was not logged in, and also display a requireLoginAlert to the user in the '/' page after the user was redirected. However, I got the following error when I try to include the setRequireLoginAlert(true) function in requireAuth.js.
Warning: Cannot update a component (`AuthProvider`) while rendering a different component (`RequireAuth`). To locate the bad setState() call inside `RequireAuth`
My requireAuth.js:
const RequireAuth = () => {
const { auth, setRequireLoginAlert } = useAuth();
const location = useLocation();
return (
auth?.user
? <Outlet />
: <Navigate to='/' state={{ from: location }} replace />
&& setRequireLoginAlert(true)
);
};
export default RequireAuth;
My AuthContext.js
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({});
const [requireLoginAlert, setRequireLoginAlert] = useState(false);
return (
<AuthContext.Provider value={{ auth, setAuth, requireLoginAlert, setRequireLoginAlert }}>
{children}
</AuthContext.Provider>
);
};
export default AuthContext;
I have try using useEffect the display the alert whenever the location has changed, but in this way the alert will keep popping out whenever I go to other pages, which is not ideal. I need a trigger to change the requireLoginAlert state after the user was redirected without error.
Feel free the drop a comment if you have any idea. Thanks a lot.
I think you were on the good way with the useEffect approach.
Indeed you have use the location in the dependencies array, but you also need to include a condition based on the current page so that the requireLoginAlert is not called every time
Have you tried something like the following piece of code ?
useEffect(
() => auth?.user && location === "/" && setRequireLoginAlert(true),
[auth?.user, location]
);
I'm new to React and I'm trying to make a simple redirect for a new created account. I'm trying to make it so that when a user creates an account, they get redirected to a new "loggedInHome page" otherwise if someone goes to the logged-in URL, they get redirected to the regular home page. Here's what I have tried:
I have a basic route for when the user logs in which by default has set authorization to false:
<Route path="/loggedInHome" component={() => <LoggedInHome authorized={false} />}>
I have a Component that allows the user to create an account, when created, the user is redirected to the new loggedInHome page:
if (signedUpState === 'true') {
return (
<>
<Redirect to={{ pathname: "/loggedInHome", authorized: { authorized: true } }} />
</>
)
}
And then I have my loggedInHome component. This page is for when the user has created their account and is on their dashboard page. Only accessible if the user made an acccount.
function LoggedInHome({authorized}) {
if (!authorized) {
return (
<>
<Redirect to={{ pathname: "/" }}/>
</>
);
}
else {
///.....
}
However, my authorized value is always set to false or undefined regardless of whether the user creates an account or not.
I've tried changing the redirect to include a state value:
state: { authorized: true}
and then accessing it in loggedInHome.js as such:
function LoggedInHome({authorized}) {
if(this.props.location.state.authorized) {
///.....
}
}
However, this doesn't seem to solve my problem either has the value always ends up being undefined and not true.
Any help would be appreciated.
I have been working on an inventory management system built in ReactJS with a back-end MariaDB database. The architecture includes user registration and logon modules with JWT authentication, a simple dashboard using D3, an inventory display screen using the React-Data-Table-Component, and an add inventory module. I will use the add-inventory module in the edit-inventory functionality by passing in a parameter to differentiate between add or edit mode (I have not yet implemented that parameter). A user can click on a row on the inventory display screen to pull that record up in edit mode, but currently I am not getting to the page to allow for inventory edit.
My code (inventorylist.component.jsx) looks like this:
updateRecord = row => {
this.setState({
...this.state,
selectedRow: row,
editSelectedRow: true
}, () => {
console.log('UPDATED STATE:',this.state.selectedRow)
})
}
editRecord = (event) => {
event.preventDefault();
return <Link to='/add' />
//return <Redirect to='/add' />
// return (
// <>
// <Router>
// <Switch>
// <Route path='/add' component={AddInventory} />
// </Switch>
// </Router>
// </>
// )
}
render() {
const inventoryTitle = this.props.jwt.username + "'s Inventory"
return (
<>
<DataTable
title={inventoryTitle}
striped={true}
highlightOnHover={true}
fixedHeader={true}
pagination={true}
paginationPerPage={10}
onRowClicked={this.updateRecord}
columns={columns}
data={this.state.inventory}
/>
<button type='button' onClick={this.editRecord}>Edit</button>
</>
)
}
This is not navigating to the add-inventory component. I have put a console.log into the editRecord function to verify that it is getting into that function after clicking on the button, but that's as far as it seems to be going.
I'm sure I'm just missing something fairly obvious here, but does anyone have any suggestions?
You can use history object to navigate imperatively:
history.push(<path>);
Read more about history
The problem is that you are return a Link which is a JSX element. What you need to do is one of the following:
If you want to keep the button element, you can push a new route into the history like this: history.push('/edit')
If you want to use Link, you need to replace you button with it and, when you click the link, it will automatically navigate there
With Hooks
If you were using hooks, you could use the useHistory hook provided by react-router to navigate to a different route doing something like this:
const history = useHistory();
const editRecord = () => {
history.push('/app');
};
I am working on a react application in which I have 3 component, AdminShopRoutes, AdminShop and Products.
The AdminShopRoutes Component:
const AdminShopRoutes = () => {
return (
<Router>
<Header>
<AdminShop exact path='/admin/shop' component={Dashboard} />
<AdminShop exact path='/admin/shop/customers' component={Customers} />
<AdminShop exact path='/admin/shop/products' component={Products} />
</Header>
</Router>)
}
The AdminShop Component
const AdminShop = ({ component: Component, ...rest }) => {
return (
<Route {...rest} render={(props) => (
true
? <Component {...props} />
: null
)} />
)
}
And finally the Product Component
const Products = (props) => {
useEffect(() => props.getProducts(), [])
const { products, loading } = props
return ( ... )
}
export default connect(mapStateToProps, { getProducts })(Products)
The links present in the other components beside these work i.e the url is changed but the page is blank as soon as the url changes for every route. If I then refresh the page, it renders fine but only on REFRESH. Also if I omit the route rendering the Products component all other routes work fine. Is there some other method of using hooks when working with react router because it is something to with Products component. Any help would be appreciated.
This is the warning I get when i render the product page
Arrow functions without {} will return the value of their one statement.
Arrow function with {} require an explicit return statement to return a value.
So the result of props.getProducts() is being returned from your effect.
However, useEffect() restricts the return value to only be a cleanup function for that effect. A non function return value is considered to be an error by React.
To fix this, just add {} to your arrow function so that it does not return a value:
useEffect(() => {
props.getProducts()
}, [])
do this
useEffect(() => {
props.getProducts()
}, [])
so that props.getProducts() doesn't get returned
Your error shows this has nothing to do with routing, but your useEffect is returning something when it shouldn't.
The error is pretty verbose and clear.
I have a parent component that should render another component when the URL is matches a certain path:
const View: React.SFC<Props> = ({
....
}) => {
return (
<div>
....
<Route path={jobPath} component={JobPanel} />} />
</div>
);
};
JobPanel.tsx will render if jobPath === /careers/:id which all works.
JobPanel.tsx has a link that will currently go back with this.props.history.push(/careers)
<BackLink
to="/company/careers"
onClick={(e: any) => { handleClose(); }}
>
<StyledChevron orientation={Orientation.Left} />
Go Back
</BackLink>
or
<BackLink
onClick={(e: any) => { this.props.history.push('/careers/); handleClose(); }}
>
<StyledChevron orientation={Orientation.Left} />
Go Back
</BackLink>
The problem is that JobPanel is supposed to have a transition in and out of the page with this Component:
class JobPanel extends Component {
render() {
const { isOpen, handleClose, job } = this.props;
return (
<StyledFlyout
active={isOpen}
Where isOpen is a boolean value in redux store.
While rendering JobPanel all works, I believe react-router is causing the page to re-render whenever the URL is changed. I'm not entirely sure on how to achieve no re-rendering.
Use the render prop instead of component in the Route. eg:
<Route path={jobPath} render={({ history }) => (
<JobPanel {...routeProps} />
)} />
From https://reacttraining.com/react-router/web/api/Route/component:
When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop (below).
For more details on using render see https://reacttraining.com/react-router/web/api/Route/render-func for more details.