After login it won't to navigate me to the other page [duplicate] - javascript

This question already has answers here:
How to create a protected route with react-router-dom?
(5 answers)
Closed 21 days ago.
I have a problem now because it won't navigate me after I click log in, it takes cookies, but won't relocate. You can show my components look right now. Everything is fine, except when I click log in, it won't relocates me to the home page.
Components:
<App /> component
function App() {
const [userData, setUserData] = useState();
const [login, setLogin] = useState()
return (
<div className="App">
<Router>
<Routes>
<Route element={<PrivateRoutes login={login} userData=
{userData} />}>
<Route element={<Home />} path="/" exact />
<Route element={<Products />} path="/products" />
</Route>
<Route element={<Login setUserData={setUserData} setLogin=
{setLogin} />}
path="/login" />
</Routes>
</Router>
</div>
);
}
<Login /> component
const Login = ({ setUserData }) => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [checking, setChecking] = useState(true)
const [loginStatus, setLoginStatus] = useState("");
Axios.defaults.withCredentials = true;
const login = (e) => {
e.preventDefault();
Axios.post("http://localhost:3001/login", {
username: username,
password: password,
}).then((response) => {
setUserData(response.data);
navigate("/");
});
};
useEffect(() => {
if (userData?.loggedIn) {
navigate("/");
}
}, [userData]);
}
return (
<div>
<div>Prijava</div>
<div>
<form onSubmit={login}>
<label>Korisničko ime</label>
<input
type="text"
onChange={(e) => {
setUsername(e.target.value);
}}
/>
<label>Lozinka</label>
<input
type="password"
onChange={(e) => {
setPassword(e.target.value);
}}
></input>
<button>Prijavi se</button>
</form>
</div>
</div>
);
};
<PrivateRoutes /> component
const PrivateRoutes = ({userData, login}) => {
return !login ? <p>Checking...</p>: userData?.loggedIn ? <Outlet
/> : <Navigate to="/login" />;
};
Console log error, after implementing code.
Newest App and PrivateRoutes version

When the page loads, userData is not set, because the useEffect runs after the JSX is rendered. Hence your condition is falling to <Navigate to="/login" /> instead of <Outlet/>.
You could use an additional state, called checking, for example, use it to display some loading message while the data is fetched.
Also, move the useEffect to check the login state on load inside PrivateRoutes. Changes your components as follows.
App:
function App() {
const [userData, setUserData] = useState();
const [checking, setChecking] = useState(true);
useEffect(() => {
Axios.get("http://localhost:3001/login")
.then((response) => {
if (response.data.loggedIn == true) {
setUserData(response.data);
}
return;
})
.catch((error) => {
console.log(error);
})
.finally(() => {
setChecking(false);
});
}, []);
return (
<div className="App">
<Router>
<Routes>
<Route element={<PrivateRoutes userData={userData} checking={checking} />}>
<Route element={<Home />} path="/" exact />
<Route element={<Products />} path="/products" />
</Route>
<Route element={<Login setUserData={setUserData} userData={userData} />} path="/login" />
</Routes>
</Router>
</div>
);
}
PrivateRoutes:
const PrivateRoutes = ({ userData, checking }) => {
return checking ? <p>Checking...</p> : userData?.loggedIn ? <Outlet /> : <Navigate to="/login" />;
};
Login:
import { useNavigate } from "react-router-dom";
const Login = ({ setUserData, userData }) => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const navigate = useNavigate();
Axios.defaults.withCredentials = true;
const login = (e) => {
e.preventDefault();
Axios.post("http://localhost:3001/login", {
username: username,
password: password,
}).then((response) => {
setUserData(response.data);
});
};
useEffect(() => {
if (userData) {
navigate("/");
}
}, [userData]);
return (
<div>
<form>
<div>Prijava</div>
<div>
<form onSubmit={login}>
<label>Korisničko ime</label>
<input
type="text"
onChange={(e) => {
setUsername(e.target.value);
}}
/>
<label>Lozinka</label>
<input
type="password"
onChange={(e) => {
setPassword(e.target.value);
}}
></input>
<button>Prijavi se</button>
</form>
</div>
</form>
</div>
);
};

To resolve the issue with the userData state being undefined during the initial render, you can initialize it with a default value in the state declaration, for example:
const [userData, setUserData] = useState({ loggedIn: false });
Additionally, you can wrap the conditional rendering in a useEffect hook that depends on userData:
useEffect(() => {
if (userData?.loggedIn) {
// Render the Outlet component
} else {
// Render the Navigate component
}
}, [userData]);

Related

React value passing from one component to another

I get a username from "login.jsx" and i want to pass it to the "App.js" to access in everywhere. How can I do that?
function App() {
const [user, setUser] = useState(null);
useEffect(() => {
}, []);
return (
<BrowserRouter>
<Offer/>
<Navigationbar/>
<Route path="login" element={user ? <Navigate to="/courses" /> : <Login />} />
</Routes>
<Footer />
</BrowserRouter>
);
}
you can pass callback function to Login component as props and call this prop inside Login component and pass user name as argument, then inside callback function you call setUser function to update the value of user
function App() {
const [user, setUser] = useState(null);
const updateUser = (value) => {
setUser(value);
}
useEffect(() => {
}, []);
return (
<BrowserRouter>
<Offer/>
<Navigationbar/>
<Route path="login" element={user ? <Navigate to="/courses" /> : <Login updateUser={updateUser} />} />
</Routes>
<Footer />
</BrowserRouter>
);
}
Here is How you can do that
App.jsx
function App() {
const [user, setUser] = useState(null);
useEffect(() => {
}, []);
const updateUser = (newUser) => {
setUser(newUser);
}
return (
<BrowserRouter>
<Offer/>
<Navigationbar/>
<Route path="login" element={user ? <Navigate to="/courses" /> : <Login onNewUser={updateUser} />} />
</Routes>
<Footer />
</BrowserRouter>
);
}
In Login.jsx when you get new user call props.updateUser(user)
You can do that with React Context;
Basically, contexts are global states.
You can read more here : https://reactjs.org/docs/context.html
It allows you to wrap your <App /> into a context provider and pass to your wrapper a value that you will be able to user in your App.

How to /WAIT A SECOND/ to navigate after user signed in in a restricted route?

So what I mean is, I have a functionality of after user signed in, navigate him/her to the homepage in a second, such as:
const handleSignIn = async (e) => {
e.preventDefault();
const user = await signIn(formData);
if (user) {
toast.success('Signed In!');
setTimeout(() => {
navigate('/');
}, 1000);
} else {
toast.error('Bad user credentials!');
}
};
But, I built a protected route, such as:
function App() {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setIsLoading(true);
if (user) {
setUser(user);
} else {
setUser(null);
}
setIsLoading(false);
});
return unsubscribe;
}, []);
if (isLoading) return <Spinner />;
return (
<>
<Router>
<Routes>
<Route path='/' element={<PrivateRoute />}>
<Route index element={<Explore />} />
<Route path='/offers' element={<Offers />} />
<Route path='/profile' element={<Profile />} />
<Route path='/contact/:landlordId' element={<Contact />} />
<Route path='/create-listing' element={<CreateListing />} />
<Route path='/category/:categoryName' element={<Category />} />
<Route path='/category/:categoryName/:id' element={<Listing />} />
</Route>
<Route
path='/sign-in'
element={!user ? <SignIn /> : <Navigate to='/' />}
/>
<Route
path='/sign-up'
element={!user ? <SignUp /> : <Navigate to='/' />}
/>
<Route
path='/forgot-password'
element={!user ? <ForgotPassword /> : <Navigate to='/' />}
/>
</Routes>
{user && <Navbar />}
</Router>
<ToastContainer autoClose={1000} />
</>
);
}
export default App;
So the problem here is, whenever user signs in or signs out,the onAuthStateChanged gets executed, therefore it enforce app to re-render in order to understand who's logged in and logged out then behave accordingly, like restrict some routes and allow others.
When user signs in, I'd like to show some toast message BUT it triggers the App level state and before I show toast and navigate user, it automatically navigates user into the homepage due to the restriction, how can I wait a second before doing that?
Can't you just wait for x amount of time before calling setUser()? That is because setUser() updates the user value, which triggers navigation.
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setIsLoading(true);
setTimeout(() => {
if (user) {
setUser(user);
} else {
setUser(null);
}
setIsLoading(false);
}, 1000);
});
return unsubscribe;
}, []);
Alternatively, use async/await with a custom wait async function that will avoid nesting:
const wait = async (d) => new Promise(r => setTimeout(d, r));
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async (user) => {
setIsLoading(true);
await wait(1000);
if (user) {
setUser(user);
} else {
setUser(null);
}
setIsLoading(false);
});
return unsubscribe;
}, []);
Allow the SignIn, component to handle the redirect itself. You have two different pieces of logic that are redirecting to "/"
Instead of this:
<Route
path='/sign-in'
element={!user ? <SignIn /> : <Navigate to='/' />}
/>
Switch to this:
<Route
path='/sign-in'
element={<SignIn />}
/>
If that causes a problem (ie, if you want to bypass SignIn and go right to / if the user is already logged in), you should add a useEffect call to SignIn and call navigate('/'); there if necessary.

React - <Link> tag displays the dynamic url in browser but does not render the page unless the browser is refreshed

I am a newbie trying to build a React blog. Routes etc work fine except when a dynamic route comes into question. The Link does not render unless refreshed.
Here is App.js where the Routes are defined
function App() {
const [username, setUsername] = useState('');
const [loggedIn, setLoggedIn] = useState(false);
return (
<>
<Router history={customHistory}>
<div className="App">
<UserContext.Provider value={{ username, setUsername, loggedIn, setLoggedIn }}>
<Navigation />
<Switch>
<Route path="/login" component={Login} />
<Route path="/register" component={Register} />
<Route exact path="/blog/:id" component={BlogDetails} />
<Route path="/content" component={Content} />
<Route path="/logout" component={Logout} />
<Route exact path="/" component={Home} />
</Switch>
</UserContext.Provider>
</div>
</Router>
</>
)
}
export default App;
Here is BlogItems.js which calls the :
import { Link } from 'react-router-dom';
function BlogItems() {
const [blogList, setBlogList] = useState([])
useEffect(() => {
Axios.get("http://localhost:3001/api/get"
, { withCredentials: true })
.then((res) => {
setBlogList(res.data);
});
}, []);
return (
<div className="blog-list">
{blogList && blogList.map((blog) => {
return (
<div className="blog-elems" key={blog && blog.id}>
<Link to={`/blog/${blog.id}`}>{blog.title}</Link>
<h6>By <span>Ritu Rawat</span> on {DateFormater(blog.dated)}</h6>
<div className="blog-list-body">{blog && blog.body.slice(0, 200) + '...'}</div>
</div>
);
})}
</div>
) }
export default BlogItems;
and this is the actual BLogDetails Page which needs to be called from the dynamic route:
const BlogDetails = (props) => {
console.log(props);
//const { username } = useContext(UserContext);
const [blog, setBlog] = useState(null);
const { id } = useParams();
useEffect(() => {
console.log("BLOG ARTICLE");
Axios.get(`http://localhost:3001/api/blog/${id}`
, { withCredentials: true })
.then((res) => {
setBlog(res.data[0]);
});
}, [id]);
return (
<>
<div className="blog-background">
<div className="blog-list">
<div className="blog-elems" key={blog && blog.id}>
<h2> {blog && blog.title}</h2>
<h6>By <span>Ritu Rawat</span> on {blog && DateFormater(blog.dated)}</h6>
<div className="blog-list-body">{blog && blog.body}</div>
</div>
<Comments id={id} />
</div>
</div>
</>
)
}
export default BlogDetails;

ReactJS: Redirecting to a new page within an axios request

I am building a MERN app with a login/register system and I am stuck on being able to redirect a user to a confirmation page which then prompts them to login.
It seems like I could use the useHistory hook in react-router-dom and do history.push() within my axios request which is within my register function:
function handleRegister(e) {
let history = useHistory();
e.preventDefault();
// Grab state
const user = {
username: formState.username,
email: formState.email,
password: formState.password,
password2: formState.password2,
};
// Post request to backend
axios
.post("http://localhost:4000/register", user)
.then((res) => {
// Redirect user to the /thankyouForRegistering page which prompts them to login.
history.push("/thankyouForRegistering");
})
.catch((err) => {
console.log(err);
});
}
But this does not work. I get an error back saying:
React Hook "useHistory" is called in function "handleRegister" which is neither a React function component or a custom React Hook function
Upon further research, it seems that in order to use the useHistory hook, it has to be within <Router>(possibly?) or directly on an onClick handler.
So something like this:
<Button onClick={() => history.push()}></button>
I can't really do that though, because I am not using onClick for my register button, I am using onSubmit and my own register function.
I also looked into using <Redirect />, so I tried making a new state called authorized, set the authorize state to true in my axios request, and then tried this:
<Route
path="/thankyouForRegistering"
render={() => (
authorized ? (
<ThankyouForRegistering />
) : (
<Redirect to="/register" />
))
}
/>
But this is not working either, and it also does not give me any kind of error.
Does anyone know the best way to redirect a user to a new page upon registering/logging in? I've been struggling with this for going on two weeks.
Thanks!!
EDIT: Here is the entire component - it's a bit messy but if anyone needs any explanations please let me know.
import React, { useState } from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Redirect,
useHistory,
} from "react-router-dom";
let navMenu;
function App() {
let history = useHistory();
const [navMenuOpen, setNavMenuOpen] = useState(false);
const [loggedIn, setLoggedIn] = useState(false);
const [errorMsg, setErrorMsg] = useState("");
const [token, setToken] = useState("");
const [authorized, setAuthorized] = useState(false);
const initialState = {
username: "",
email: "",
password: "",
password2: "",
};
const [formState, setFormState] = useState(initialState);
const { username, email, password, password2 } = formState;
const handleChange = (e) => {
setFormState({ ...formState, [e.target.name]: e.target.value });
};
function handleRegister(e) {
//const history = useHistory();
e.preventDefault();
// Grab setState
const user = {
username: formState.username,
email: formState.email,
password: formState.password,
password2: formState.password2,
};
// Post request to backend
axios
.post("http://localhost:4000/register", user)
.then((res) => {
console.log(user);
history.push("/thankyouForRegistering");
setAuthorized(true);
// Redirect user to the /thankyouForRegistering page which prompts them to login.
})
.catch((err) => {
console.log(err);
});
// Once a user has registered, clear the registration form and redirect the user to a page that says thank you for registering, please login.
}
const handleLogin = (e) => {
e.preventDefault();
// Grab setState
const userData = {
email: formState.email,
password: formState.password,
};
axios
.post("http://localhost:4000/login", userData)
.then((res) => {
// Get token from local storage if there is a token
localStorage.setItem("token", res.data.token);
// If there is a token, redirect user to their profile and give them access to
// their recipeList and shoppingList
setLoggedIn(true);
//props.history.push("/profile");
})
.catch((err) => console.log(err));
};
const navMenuToggle = () => {
console.log("toggle");
setNavMenuOpen(!navMenuOpen);
};
const navMenuClose = () => {
setNavMenuOpen(false);
};
const logoutFromNavMenu = () => {
setLoggedIn(false);
navMenuClose();
};
return (
<Router>
<div>
<Navbar
loggedIn={loggedIn}
navMenuToggle={navMenuToggle}
/>
<NavMenu
loggedIn={loggedIn}
show={navMenuOpen}
navMenuClose={navMenuClose}
logoutFromNavMenu={logoutFromNavMenu}
/>
<Switch>
<Route
path="/login"
render={(props) => (
<Login
handleLogin={handleLogin}
handleChange={handleChange}
email={email}
password={password}
errorMsg={errorMsg}
/>
)}
/>
<Route
path="/register"
render={(props) => (
<Register
handleRegister={handleRegister}
handleChange={handleChange}
email={email}
username={username}
password={password}
password2={password2}
errorMsg={errorMsg}
/>
)}
/>
<Route
path="/profile"
render={() => (loggedIn ? <Profile /> : <Redirect to="/login" />)}
/>
<Route
path="/thankyouForRegistering"
render={() =>
authorized ? (
<ThankyouForRegistering />
) : (
<Redirect to="/register" />
)
}
/>
<Route
path="/recipes"
render={(props) =>
loggedIn ? <RecipeBook /> : <Redirect to="/login" />
}
/>
<Route
path="/list"
render={(props) =>
loggedIn ? <ShoppingList /> : <Redirect to="/login" />
}
/>
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
<Route
path="/accountSettings"
render={(props) =>
loggedIn ? <AccountSettings /> : <Redirect to="/login" />
}
/>
<Route
exact
path="/"
component={() => <Home isLoggedIn={loggedIn} />}
/>
</Switch>
</div>
</Router>
);
}
export default App;
I figured out what the problem was. It wasn't enough to be using useHistory, I had to be using withRouter to get access to the history prop.
So I imported withRouter to my App component:
import { withRouter } from 'react-router-dom';
Then added this at the very bottom of the page, right below the App function:
const AppWithRouter = withRouter(App);
export default AppWithRouter;
So the App now must be exported as an argument of withRouter to get access to the history prop (as well as location and params I think?)
But this still wasn't enough. It was giving me an error saying that withRouter can only be used within <Router />(or <BrowserRouter />, whichever one you are using). So I wrapped my main <App /> in index.js with <BrowserRouter />
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
And imported it of course.
This still wasn't enough, though. After adding
history.push("/thankYouForRegistering);
to my axios request, it added the route to the URL, but it did not re render the view. It was still stuck on the register page. Then I realized I had <BrowserRouter /> in two different places now. In my App component, I still had it wrapped around all of my routes, so after removing that and just returning a div with <Switch /> wrapped around all of my routes, that made it work like expected.
I hope this helps somebody who is facing the same issue!

React Router not rendering page

I have a component called admin it is a form that will redirect me to another page rendering not working though.
Router main
const globalState = {
isAuthed: false,
token: null,
};
export const AuthContext = React.createContext(globalState);
function App() {
const [currentUser, setCurrentUser] = useState(globalState)
return (
<AuthContext.Provider value={[currentUser, setCurrentUser]}>
<Router>
<Switch>
<Route exact path="/admin" component={Admin} />
<Route exact path="/admin-panel" component={Pannel} />
</Switch>
</Router>
</AuthContext.Provider>
)
}
export default App;
admin component
const LoginForm = () => {
const [state, setState] = useContext(AuthContext)
const login = (state) => {
const user = document.getElementById('user').value;
const pass = document.getElementById('pass').value;
const request = {
user,
pass
}
console.log(request)
fetch('/api/admin', {
method: "post",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(request),
})
.then(res => res.json())
.then(res => {
if(res.auth){
valid(5000,"Login Success. Redirecting in 3 second")
setTimeout(() =>{
setState({isAuthed: res.auth, token: res.key})
}, 3000)
}
else{
warn(5000,res.message)
}
})
}
return(
<div style={css}>
<ToastContainer />
{(state && state.isAuthed)? <Redirect to='/adming-panel'/>: false}
<h1 style={{color: "teal"}}>Admin Panel</h1>
<Form id="login-form" size='large' style={{backgroundColor: "white"}}>
<Segment stacked>
<Form.Input id="user" fluid icon='user' iconPosition='left' placeholder='E-mail address' />
<Form.Input
fluid
icon='lock'
iconPosition='left'
placeholder='Password'
type='password'
id="pass"
/>
<Button onClick={() => login(state)} color='teal' fluid size='large'>
Login
</Button>
</Segment>
</Form>
</div>
)
}
export default LoginForm
the new page that I want to render
const Pannel = () => {
const [state, setState] = useContext(AuthContext)
return (
<div>
{(!state || !state.isAuthed)? <Redirect to='/adming-panel'/>: false}
Secret Page
</div>
)
}
export default Pannel
All the answers that I searched for. Was to put the exact keyword before the path but still, the component won't render only an empty white screen appears and no errors on console or backend console.
<Route exact path="/admin-panel" component={Pannel} />
Spot the key difference
<Redirect to='/adming-panel'/>
You are welcome.

Categories

Resources