the logout component not rendering after the authenticated is turned to true this is similar code as from react-router docs - javascript

I just tried to build the react-router docs ex on browser but there is problem in AuthButton component it isn't showing signOut button when the isAuthenticated turns true
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect,
useHistory,
useLocation,
} from 'react-router-dom';
export default function AuthExample() {
return (
<Router>
<div>
<AuthButton />
<ul>
<li>
<Link to='/public'>Public Page</Link>
</li>
<li>
<Link to='/protected'>Protected Page</Link>
</li>
</ul>
<Switch>
<Route path='/public'>
<PublicPage />
</Route>
<Route path='/login'>
<LoginPage />
</Route>
<PrivateRoute path='/protected'>
<ProtectedPage />
</PrivateRoute>
</Switch>
</div>
</Router>
);
}
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
fakeAuth.isAuthenticated = true;
setTimeout(cb, 100); // fake async
},
signout(cb) {
fakeAuth.isAuthenticated = false;
setTimeout(cb, 100);
},
};
function AuthButton() {
let history = useHistory();
return fakeAuth.isAuthenticated ? (
<p>
Welcome!{' '}
<button
onClick={() => {
fakeAuth.signout(() => history.push('/'));
}}>
Sign out
</button>
</p>
) : (
<p>You are not logged in.</p>
);
}
function PrivateRoute({ children, ...rest }) {
return (
<Route
{...rest}
render={({ location }) =>
fakeAuth.isAuthenticated ? (
children
) : (
<Redirect
to={{
pathname: '/login',
state: { from: location },
}}
/>
)
}
/>
);
}
function PublicPage() {
return <h3>Public</h3>;
}
function ProtectedPage() {
return <h3>Protected</h3>;
}
function LoginPage() {
let history = useHistory();
let location = useLocation();
let { from } = location.state || { from: { pathname: '/' } };
let login = () => {
fakeAuth.authenticate(() => {
history.replace(from);
});
};
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={login}>Log in</button>
</div>
);
}

The reason it's not updating is because it doesn't know to update. You change the route but AuthButton doesn't know to re-render based on the route you need to pass it a prop so that it knows when to update. I refactored your code to incorporate using react hooks. By using hooks you can store isAuthenticated in local state in AuthExample via useState.
From AuthExample, pass down the state value for isAuthenticated as a prop to AuthButton. If the prop changes, AuthButton will detect it and this will trigger a re-render of AuthButton and reflect the correct component structure you are looking for. See below.
import React, { useState } from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect,
useHistory,
useLocation
} from "react-router-dom";
export default function AuthExample() {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const fakeAuth = {
isAuthenticated: isAuthenticated,
authenticate(cb) {
fakeAuth.isAuthenticated = true;
setIsAuthenticated(true);
setTimeout(cb, 100); // fake async
},
signout(cb) {
setIsAuthenticated(false);
fakeAuth.isAuthenticated = false;
setTimeout(cb, 100);
}
};
return (
<Router>
<div>
<AuthButton fakeAuth={fakeAuth} isAuthenticated={isAuthenticated} />
<ul>
<li>
<Link to="/public">Public Page</Link>
</li>
<li>
<Link to="/protected">Protected Page</Link>
</li>
</ul>
<Switch>
<Route path="/public">
<PublicPage />
</Route>
<Route path="/login">
<LoginPage fakeAuth={fakeAuth} />
</Route>
<PrivateRoute path="/protected" fakeAuth={fakeAuth}>
<ProtectedPage />
</PrivateRoute>
</Switch>
</div>
</Router>
);
}
function AuthButton(props) {
const { fakeAuth, isAuthenticated } = props;
let history = useHistory();
return isAuthenticated ? (
<p>
Welcome!{" "}
<button
onClick={() => {
fakeAuth.signout(() => history.push("/"));
}}
>
Sign out
</button>
</p>
) : (
<p>You are not logged in.</p>
);
}
function PrivateRoute({ children, ...rest }) {
const { fakeAuth } = rest;
return (
<Route
{...rest}
render={({ location }) =>
fakeAuth.isAuthenticated ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
);
}
function PublicPage() {
return <h3>Public</h3>;
}
function ProtectedPage() {
return <h3>Protected</h3>;
}
function LoginPage(props) {
const { fakeAuth } = props;
let history = useHistory();
let location = useLocation();
let { from } = location.state || { from: { pathname: "/" } };
let login = () => {
fakeAuth.authenticate(() => {
history.replace(from);
});
};
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={login}>Log in</button>
</div>
);
}
You can also see a working example in this code sandbox. There are a few ways to do this but hooks make it easy to manipulate state values to update functional components without having to make them class components. This way also keeps most of your code intact as is just adding a few checks for when isAuthenticated is updated.

I think the problem is in rendering process.
In my opinion, if you put the sub-functions in to the exported function, this problem may solve.
If the problem won't solve, try the class base component for handling this rendering process.
wish you success

Related

React context api lose auth data when react router dom push page

I have an context where i save the user data, and i have another component when verify the context user is null, if the context user is null my component should redirect the user to the login page, if not should render the component. My routers is inside my Authprovider, but still losing the user data when reload the router. I found another posts with the same issue, and the instruction is to keep the routers inside the useauthprovider, but doesn't work with my app.
My code
function App() {
let header = window.location.pathname === '/login' || '/cadastro' ? <Header /> : null;
let footer = window.location.pathname === '/login' || '/cadastro' ? <Footer /> : null;
return (
<UseAuthProvider> // My use AuthProvider
<Router>
<div className='app-container' >
<Switch>
<Cart>
<Header />
<NavbarMenu />
<div className='app-body'>
<UseCampanhaProvider>
<PublicRoute exact path='/' component={Home} />
<PrivateRoute exact path='/cupom/:campaignId' component={CupomScreen} />
<PrivateRoute exact path='/carrinho' component={CartScreen} />
</UseCampanhaProvider>
<PublicRoute exact path='/login' restricted={true} component={Login} />
<PublicRoute path='/cadastro' restricted={true} component={Cadastro} />
</div>
<AuthModal />
{footer}
</Cart>
</Switch>
</div>
</Router >
</UseAuthProvider>
);
}
export default App;
My component where i verify the user context
const PrivateRoute = ({ component: Component, ...rest }) => {
const { user } = useAuth();
return (
<Route {...rest} render={props => (
!user ?
<Redirect to='/login' />
:
<Component {...props} />
)} />
);
};
export default PrivateRoute;
My context where i load the user
const UseAuthProvider = ({ children }) => {
const [user, setUser] = useState();
const [open, setOpen] = useState(false)
useEffect(() => {
verifyUser(); //here i call the function when verify the localstorage
}, [])
const verifyUser = async () => {
let tokenHeader = authHeader();
if (tokenHeader) {
await Api.post('/cliente/index', {}, {
headers: {
...tokenHeader
}
}).then((response) => {
setUser(response.data.cliente)
})
}
}
const handleModal = () => {
setOpen((state) => !state)
}
const Logout = async () => {
localStorage.clear('acessToken-bolao')
setUser(null)
}
return (
<useAuthContext.Provider value={{ Auth, verifyUser, user, Register, Logout, open, handleModal }}>
{children}
</useAuthContext.Provider>
)
}
I tried to debug my application and when i redirect my user to another router, before the component render my user return undefined, and after my component is rendered the context load the user data.
It sounds like your entire application is unmounting and remounting.
In this case the state will be lost as it is not simply a re-render.
By what mechanism are you navigating to the new page?
If I remember React-Router correctly you need to use
If you try navigating the url itself with window.location or href then you are reloading the entire page (not using the router in the SPA)
If routed correctly I would expect that only data inside the Switch would be re-loaded.

How to pass location state and URL params using React-Router?

When I click on the link in the HoverBooks Component to get to a new page where I can render the book location state in Book component, but when I press on it nothing happens. I think the error is in Route:
function App() {
return (
<div className="App">
<Router>
<Switch>
<Route path="/book:/book.Key">
<Book />
</Route>
<Route path="/signin">
<Signin />
</Route>
<Route path="/">
<Header />
<Home />
</Route>
</Switch>
</Router>
</div>
)
}
export default App
import React from 'react'
import { useLocation } from 'react-router-dom'
const Book = () => {
const {
state: { book },
} = useLocation()
console.log({ book })
return (
<div key={book.key}>
<h1>{book.bookName}</h1>
</div>
)
}
export default Book
const HoverBooks = ({ ...book }) => {
const [inHoverBooks, setInHoverBooks] = React.useState(false)
return (
<>
<Link
to={{
pathName: `/book/${book.key}`,
state: {
book,
},
}}
>
<img
onMouseLeave={() => setInHoverBooks(false)}
onMouseEnter={() => setInHoverBooks(true)}
src={book.image}
key={book.key}
/>
</Link>
{inHoverBooks && (
<div className="hover__containter">
<h3>{book.bookName}</h3>
<h2>{book.by}</h2>
<h2>{book.Narreted}</h2>
<h2>{book.length}</h2>
<h2>{book.rating}</h2>
</div>
)}
</>
)
}
export default HoverBooks
Below is the correct form, e.g. /:someName, to define a route with URL params:
<Route path="/book/:bookKey">
<Book />
</Route>
And here is the right syntax to make a Link for the above route:
<Link
to={{
pathname: `/book/SOME_BOOK_KEY`, // replace SOME_BOOK_KEY with some value
state: {
book, // e.g. const book = { key: 'js', bookName: 'Learn JavaScript'}
},
}}
>
<img src="some_src" alt="something" />
</Link>
And you useParams and useLocation react-hooks to access the "URL params" and "location state" in a component:
const Book = () => {
const {
state: { book },
} = useLocation()
const { bookKey } = useParams();
console.log(book, bookKey)
// prints "book" object (from location state) and "bookKey" (from URL params)
return (
<div key={book.key}>
<h1>{book.bookName}</h1>
</div>
)
}
I would suggest you to add typescript to your ReactJS app. It helps you find errors early by doing "static Type-checking".
With react router you need to pass the component you want to render to the Route like this
const ComponentA = (props) => {...}
<Route path="/component-a" component={ComponentA} />
And here is how to link to component a
<Link to="/component-a" >Go to component A</Link>

React - useContext returns undefined

i'm trying to use React Context to manage authentication, but i can't see the value that return the context in PrivateRoute.js
App.js
render() {
return (
<>
<BrowserRouter>
<Islogin>
<Header/>
<Banner/>
<Switch>
<PrivateRoute exact path="/index" component={Landing} />
<PrivateRoute path="/upload" component={Upload} exact />
<PublicRoute restricted={false} path="/unauth" component={Unauthorized} exact />
</Switch>
</Islogin>
</BrowserRouter>
</>
);
}
}
export default App;
the console log of isAuthenticated returns undefined
PrivateRoute.js
const PrivateRoute = ({component: Component, ...rest}) => {
const isAuthenticated = useContext(AuthContext)
console.log(isAuthenticated)
const [validCredentials, setValidCredentials] = React.useState(false)
React.useEffect(() => {
if (typeof isAuthenticated === 'boolean') {
setValidCredentials(isAuthenticated)
}
}, [isAuthenticated])
return (
// Show the component only when the user is logged in
// Otherwise, redirect the user to /signin page
<Route {...rest} render={props => (
validCredentials ?
<Component {...props} />
: <Redirect to="/unauth" />
)} />
);
};
export default PrivateRoute;
IsLogin.js
The api call works and the console log shows true.
export default function Islogin({ children }) {
var [auth, setAuth] = React.useState(false)
React.useEffect(() =>{
axios.post('/api/auth').then(response => {
var res = response.data.result;
console.log("try")
console.log(res)
setAuth(res)
})
},[])
return (
<AuthContext.Provider value={auth}>
{children}
</AuthContext.Provider>
)
}
You may need to import it at the top of the file that you are using it in (PrivateRoute.js)
Try this:
import {useContext} from 'react'

React Router Redirect to component not redirecting

I need a simple redirect to a component but its not working not sure why. This is the code:
const HomePage = () => {
const [videos, setVideos] = useState([]);
const videoClicked = (video) => {
return <Redirect to='/video' />
}
if(videos === []){
return <div>Loading ...</div>
}
return (
<div>
{videos.map(video => (
<div onClick={() => videoClicked(video)}>
<VideoThumbnail video={video} />
</div>
))}
</div>
)
}
export default HomePage
I have a useEffect in my HomePage function that I didnt include in this snippet that gives videos values. It works and when I onClick the div it calls videoClicked but the redirect doesnt work.
This is my router:
const App = () => {
return (
<HashRouter>
<Switch>
<Route exact path="/video" component={VideoPage} />
<Route path="/" component={HomePage} />
</Switch>
</HashRouter>
)
}
Also when I get this working is it possible to redirect to component and pass props thru it instead of just passing a string in the to tag.
You can have a new state and redirect based on that:
const HomePage = () => {
const [videos, setVideos] = useState([]);
const [clicked, setClicked] = useState(false);
const videoClicked = (video) => {
setClicked(true);
// return <Redirect to='/video' />
}
if (videos === []) {
return <div>Loading ...</div>
}
return (
clicked ? <Redirect to={{
pathname: '/video',
state: { someData: 'test' }
}} /> : (
<div>
{videos.map(video => (
<div onClick={() => videoClicked(video)}>
<VideoThumbnail video={video} />
</div>
))}
</div>
)
)
}
export default HomePage
and you can use props.location.state.someData in the component you're redirected to.
you can consider using History HTML5 instead :) simple and straightforward

How to display component using react-router <Prompt> to prevent or allow route change

I'm currently trying to find a way to display a custom component (like a Modal) to confirm route changes using the Prompt component.
The default behavior of the Promp component is to show a confirm dialog with a message, as you can see in this Example: React Router: Preventing Transitions.
Note: I am using the <BrowserRouter> component.
The router has a prop named getUserConfirmation, which you can use to customize the behavior of the <Prompt> component.
// this is the default behavior
function getConfirmation(message, callback) {
const allowTransition = window.confirm(message);
callback(allowTransition);
}
<BrowserRouter getUserConfirmation={getConfirmation} />;
What I'm trying to do:
Inside the parent component APP
I'm setting the confirm state to true, to display the <Confirm> component
And I'm trying to pass the callback from the getConfirmation function to the <Confirm> component to call it with true to allow transition, and with false to prevent it.
The callback would be called with true or false in the default behavior as you can see above.
function getConfirmation(message, callback) {
console.log("Inside getConfirmation function...");
setConfirmCallback(callback);
setConfirm(true);
// const allowTransition = window.confirm(message);
// callback(allowTransition);
}
This is what App.js is rendering:
return (
<Router getUserConfirmation={getConfirmation}>
<AllRoutes />
{confirm && (
<Confirm confirmCallback={confirmCallback} setConfirm={setConfirm} />
)}
</Router>
);
What seems to be the problem:
The confirm dialog seems to block the function at that point. So the callback variable/parameter is still in scope. So everything works OK.
When I remove the confirm dialog, that function runs all the way. And when I click on the confirm button inside the <Confirm> component, the callback no longer exists.
QUESTION
Does anybody know a way to achieve this behavior (preventing route changes using a custom component instead of a confirm dialog) using react-router-dom?
Link to CodeSandbox
Full code from CodeSandbox:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Route,
Switch,
Link,
Prompt
} from "react-router-dom";
import "./styles.css";
function App() {
console.log("Rendering App...");
const [confirm, setConfirm] = useState(false);
const [confirmCallback, setConfirmCallback] = useState(null);
function getConfirmation(message, callback) {
console.log("Inside getConfirmation function...");
setConfirmCallback(callback);
setConfirm(true);
// const allowTransition = window.confirm(message);
// callback(allowTransition);
}
return (
<Router getUserConfirmation={getConfirmation}>
<AllRoutes />
{confirm && (
<Confirm confirmCallback={confirmCallback} setConfirm={setConfirm} />
)}
</Router>
);
}
function Confirm(props) {
function allowTransition() {
props.setConfirm(false);
props.confirmCallback(true);
}
function blockTransition() {
props.setConfirm(false);
props.confirmCallback(false);
}
return (
<React.Fragment>
<div>Are you sure?</div>
<button onClick={allowTransition}>Yes</button>
<button onClick={blockTransition}>No way</button>
</React.Fragment>
);
}
function AllRoutes(props) {
console.log("Rendering AllRoutes...");
return (
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/comp1" component={Component1} />
</Switch>
);
}
function Home(props) {
console.log("Rendering Home...");
return (
<React.Fragment>
<div>This is Home</div>
<ul>
<li>
<Link to="/comp1">Component1</Link>
</li>
</ul>
</React.Fragment>
);
}
function Component1(props) {
console.log("Rendering Component1...");
const [isBlocking, setIsBlocking] = useState(true);
return (
<React.Fragment>
<Prompt
when={isBlocking}
message={location =>
`Are you sure you want to go to ${location.pathname}`
}
/>
<div>This is component 1</div>
<Link to="/">Home</Link>
</React.Fragment>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Inspired by this discussion and by this example, I was able to make my example working.
The problem was that when the <Confirm> was being created, the setConfirmCallback() call wasn't done yet. So the <Confirm> component wasn't able to use the callback from getUserConfirmation.
So I've changed this line:
FROM:
setConfirmCallback(callback);
TO:
setConfirmCallback(()=>callback);
And now it works!
CodeSandbox Link
Full CodeSandbox code:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Route,
Switch,
Link,
Prompt
} from "react-router-dom";
import "./styles.css";
function App() {
console.log("Rendering App...");
const [confirm, setConfirm] = useState(false);
const [confirmCallback, setConfirmCallback] = useState(null);
function getConfirmation(message, callback) {
console.log("Inside getConfirmation function...");
setConfirmCallback(() => callback);
setConfirm(true);
// const allowTransition = window.confirm(message);
// callback(allowTransition);
}
return (
<Router getUserConfirmation={getConfirmation}>
<AllRoutes />
{confirm && (
<Confirm confirmCallback={confirmCallback} setConfirm={setConfirm} />
)}
</Router>
);
}
function Confirm(props) {
console.log("Rendering Confirm...");
function allowTransition() {
props.setConfirm(false);
props.confirmCallback(true);
}
function blockTransition() {
props.setConfirm(false);
props.confirmCallback(false);
}
return (
<React.Fragment>
<div>Are you sure?</div>
<button onClick={allowTransition}>Yes</button>
<button onClick={blockTransition}>No way</button>
</React.Fragment>
);
}
function AllRoutes(props) {
console.log("Rendering AllRoutes...");
return (
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/comp1" component={Component1} />
</Switch>
);
}
function Home(props) {
console.log("Rendering Home...");
return (
<React.Fragment>
<div>This is Home</div>
<ul>
<li>
<Link to="/comp1">Component1</Link>
</li>
</ul>
</React.Fragment>
);
}
function Component1(props) {
console.log("Rendering Component1...");
const [isBlocking, setIsBlocking] = useState(true);
return (
<React.Fragment>
<Prompt
when={isBlocking}
message={location =>
`Are you sure you want to go to ${location.pathname}`
}
/>
<div>This is component 1</div>
<Link to="/">Home</Link>
</React.Fragment>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I found a simple workaround for my case. I could not share the whole component but snippets.
// this will initiate the dialogbox render and
// prevent the window from going back by returning false
const backButtonPressed = async () => {
leavePrompt(false);
return false;
}
// this will open the prompt dialog box
const leavePrompt = (endRoom) => {
setOpenPrompt({open: true, action: endRoom ? "endRoom" : "leaveQuitely"});
}
// render
<Dialog open={openPrompt.open} aria-labelledby="interim-user-dialog-title">
<DialogContent dividers>
<Typography variant="h6" gutterBottom>
Are you sure?
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenPrompt({...openPrompt, open: false})} color="primary">
Stay
</Button>
<Button onClick={() => history.push("/")} color="secondary">
Leave
</Button>
</DialogActions>
</Dialog>
// when allowedToGoBack state is true then call a method that will render the dialog box
<Prompt
when={true}
title={"Alert"}
message={() => allowedToGoBack ? backButtonPressed() && false : true}
/>

Categories

Resources