how to use onSubmit in react-router while onSubmit checks the details in the form and if it's correct he pass with Link="" to another component - react-router

I'm trying to use onSubmit in the form, I wand it to check the details in the form and according to the details to decide if I want to pass to another component with Link in react-router how can I do that, how to write the Link (and it will pass only when I want in the onSubmit function)?
this is my routing-code:
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import LogIn from "./components/LogIn";
import HomePage from "./HomePage";
import Todos from "./components/Todos";
import Albums from "./components/Albums";
import Info from "./components/Info";
import Posts from "./components/Posts";
function App() {
return (
<Router>
<div>
<Routes>
<Route path="/" pelement={<HomePage />} />
<Route path="/login" element={<LogIn />} />
<Route path="/todos" element={<Todos />} />
<Route path="/info" element={<Info />} />
<Route path="/albums" element={<Albums />} />
<Route path="/posts" element={<Posts />} />
</Routes>
</div>
</Router>
);
}
export default App;
and I want that when I will click on the submit here(down- in the log in component) it will control on the Link="/" that will happen only if onSubmit decide
import { FormEventHandler } from "react";
function LogIn() {
const [inputField, setInputField] = useState({
user_name: "",
password: "",
});
const inputChange = (e) => {
e.preventDefault();
setInputField((prevState) => ({
...prevState,
[e.target.name]: e.target.value,
}));
};
const submitButton = (e) => {
e.preventDefault();
if(**the details in the form are correct**)
**add the details to local storage and pass to home page with:<Link to="/"></Link>**
else
alert("details are not correct");
};
return (
<form onSubmit={submitButton}>
<label>Enter you user name</label>
<input
type="text"
name="user_name"
onChange={inputChange}
placeholder="Your user name"
value={inputField.user_name}
/>
<br />
<label>Enter you password</label>
<input
type="password"
name="password"
onChange={inputChange}
placeholder="Your password"
value={inputField.password}
/>
<br />
<button type="submit" >Log In</button>
</form>
);
}
export default LogIn;

You can't return a Link component in a callback like this and expect it to be rendered and do anything in the DOM. In callbacks you will need to issue an imperative navigation action using the navigate function.
Example:
import { useNavigate } from 'react-router-dom';
function LogIn() {
const navigate = useNavigate();
...
const submitButton = (e) => {
e.preventDefault();
if (**the details in the form are correct**) {
// add the details to local storage
localStorage.setItem("details", details);
// and pass to home page
navigate("/", { state: { details } });
} else {
alert("details are not correct");
}
};
...

Related

Nesting React routes to login-protected pages [duplicate]

This question already has answers here:
How to create a protected route with react-router-dom?
(5 answers)
Closed 8 months ago.
I am using react-router-dom#6.3.0
I have created a React app where certain Private pages are accessible only users who have logged in.
You can find a demo here, and a GitHub repo here.
A simplified version of this is shown below.
I have wrapped every Private page in its own RequireLogin component, and this works:
<Route
path="/private1"
element={
<RequireLogin redirectTo="/">
<Private
text="Private Page #1"
/>
</RequireLogin >
}
/>
The RequireLogin component redirects to a page with the Login component if the user is not logged in, and renders the requested component only to a logged in user.
My question is this:
Is it there a way to wrap all the Private routes inside one RequireLogin component, or do I have to wrap each one separately?
import React, { createContext, useContext, useState } from 'react'
import ReactDOM from 'react-dom';
import {
BrowserRouter as Router,
Routes,
Route,
Navigate,
NavLink
} from "react-router-dom";
const UserContext = createContext()
const UserProvider = ({children}) => {
const [ loggedIn, logIn ] = useState("")
return (
<UserContext.Provider
value={{
loggedIn,
logIn
}}
>
{children}
</UserContext.Provider>
)
}
function App() {
return (
<Router>
<UserProvider>
<Routes>
<Route
path="/"
element={<NavLink to="/login">Log In</NavLink>}
/>
<Route
path="/login"
element={<Login />}
/>
<Route
path="/private1"
element={
<RequireLogin redirectTo="/login">
<Private
text="Private Page #1"
/>
</RequireLogin >
}
/>
<Route
path="/private2"
element={
<RequireLogin redirectTo="/login">
<Private
text="Private Page #2"
/>
</RequireLogin >
}
/>
</Routes>
</UserProvider>
</Router>
);
}
function Menu({hideLogOut}) {
const { loggedIn } = useContext(UserContext)
if (loggedIn) {
if (!hideLogOut) {
return <ul>
<li><NavLink to="/login">Log Out</NavLink></li>
<li><NavLink to="/private1">Private #1</NavLink></li>
<li><NavLink to="/private2">Private #2</NavLink></li>
</ul>
} else {
return <ul>
<li><NavLink to="/private1">Private #1</NavLink></li>
<li><NavLink to="/private2">Private #2</NavLink></li>
</ul>
}
} else {
return <p>Not Logged In</p>
}
}
function RequireLogin ({ children, redirectTo }) {
const { loggedIn } = useContext(UserContext);
return loggedIn
? children
: <Navigate to={redirectTo} />;
}
function Private({text}) {
return (
<div>
<Menu />
<h1>{text}</h1>
</div>
)
}
function Login() {
const { loggedIn, logIn } = useContext(UserContext)
const toggleLogged = () => {
logIn(!loggedIn)
}
return (<div>
<Menu
hideLogOut={true}
/>
<label htmlFor="loggedIn">
<input
type="checkbox"
name="loggedIn"
id="loggedIn"
checked={loggedIn}
onChange={toggleLogged}
/>
Pretend that we are logged in
</label>
</div>)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
I use a second router for the private routes, wrapped with a single <RequireLogin>. Example:
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegistrationPage />} />
<Route path="*" element={
<RequireLogin>
<Routes>
<Route path="/" element={<FeedPage />} />
<Route path="/explore" element={<ExplorePage />} />
<Route path="/user/:username" element={<UserPage />} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
</RequireLogin>
} />
</Routes>

V6 React Router Dom Routes are not working

I am trying to build a React register and login with JWT tokens and I am hitting a wall.
When I click on the register button it should take me to the login. And from there I should be able to login and see the dashboard.
When I click on register, nothing happens. I replaced useHistory with useNavigate and .history.push with navigate but still nothing.
I'm using MAMP for my db.
App.js
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Dashboard from "./components/Dashboard";
import Login from "./components/Login";
import Navbar from "./components/Navbar";
import Register from "./components/Register";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/dashboard" element={<><Navbar /><Dashboard /></>} />
</Routes>
</BrowserRouter>
);
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import "bulma/css/bulma.css";
import axios from "axios";
axios.defaults.withCredentials = true;
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Dashboard.js
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect } from 'react'
import axios from 'axios';
import jwt_decode from "jwt-decode";
import { useNavigate } from 'react-router-dom';
const Dashboard = () => {
const [name, setName] = useState('');
const [token, setToken] = useState('');
const [expire, setExpire] = useState('');
const [users, setUsers] = useState([]);
const navigate = useNavigate();
useEffect(() => {
refreshToken();
getUsers();
}, []);
const refreshToken = async () => {
try {
const response = await axios.get('http://localhost:3306/token');
setToken(response.data.accessToken);
const decoded = jwt_decode(response.data.accessToken);
setName(decoded.name);
setExpire(decoded.exp);
} catch (error) {
if (error.response) {
navigate("/");
}
}
}
const axiosJWT = axios.create();
axiosJWT.interceptors.request.use(async (config) => {
const currentDate = new Date();
if (expire * 1000 < currentDate.getTime()) {
const response = await axios.get('http://localhost:3306/token');
config.headers.Authorization = `Bearer ${response.data.accessToken}`;
setToken(response.data.accessToken);
const decoded = jwt_decode(response.data.accessToken);
setName(decoded.name);
setExpire(decoded.exp);
}
return config;
}, (error) => {
return Promise.reject(error);
});
const getUsers = async () => {
const response = await axiosJWT.get('http://localhost:3306/users', {
headers: {
Authorization: `Bearer ${token}`
}
});
setUsers(response.data);
}
return (
<div className="container mt-5">
<h1>Welcome Back: {name}</h1>
<table className="table is-striped is-fullwidth">
<thead>
<tr>
<th>No</th>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{users.map((user, index) => (
<tr key={user.id}>
<td>{index + 1}</td>
<td>{user.name}</td>
<td>{user.email}</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
export default Dashboard
Login.js
import React, { useState } from 'react'
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [msg, setMsg] = useState('');
const navigate = useNavigate();
const Auth = async (e) => {
e.preventDefault();
try {
await axios.post('http://localhost:3306/login', {
email: email,
password: password
});
navigate("/dashboard");
} catch (error) {
if (error.response) {
setMsg(error.response.data.msg);
}
}
}
return (
<section className="hero has-background-grey-light is-fullheight is-fullwidth">
<div className="hero-body">
<div className="container">
<div className="columns is-centered">
<div className="column is-4-desktop">
<form onSubmit={Auth} className="box">
<p className="has-text-centered">{msg}</p>
<div className="field mt-5">
<label className="label">Email or Username</label>
<div className="controls">
<input type="text" className="input" placeholder="Username" value={email} onChange={(e) => setEmail(e.target.value)} />
</div>
</div>
<div className="field mt-5">
<label className="label">Password</label>
<div className="controls">
<input type="password" className="input" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} />
</div>
</div>
<div className="field mt-5">
<button className="button is-success is-fullwidth">Login</button>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
)
}
export default Login
Navbar.js
import React from 'react'
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
const Navbar = () => {
const navigate = useNavigate();
const Logout = async () => {
try {
await axios.delete('http://localhost:3306/logout');
navigate("/");
} catch (error) {
console.log(error);
}
}
return (
<nav className="navbar is-light" role="navigation" aria-label="main navigation">
<div className="container">
<div className="navbar-brand">
<a className="navbar-item" href="https://bulma.io">
<img src="https://bulma.io/images/bulma-logo.png" width="112" height="28" alt="logo" />
</a>
<a href="/" role="button" className="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" className="navbar-menu">
<div className="navbar-start">
<a href="/" className="navbar-item">
Home
</a>
</div>
<div className="navbar-end">
<div className="navbar-item">
<div className="buttons">
<button onClick={Logout} className="button is-light">
Log Out
</button>
</div>
</div>
</div>
</div>
</div>
</nav>
)
}
export default Navbar
Register.js
import React, { useState } from 'react'
import axios from "axios";
import { useNavigate } from "react-router-dom";
const Register = () => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confPassword, setConfPassword] = useState('');
const [msg, setMsg] = useState('');
const navigate = useNavigate();
const Register = async (e) => {
e.preventDefault();
try {
await axios.post('http://localhost:3306/users', {
name: name,
email: email,
password: password,
confPassword: confPassword
});
navigate("/");
} catch (error) {
if (error.response) {
setMsg(error.response.data.msg);
}
}
}
return (
<section className="hero has-background-grey-light is-fullheight is-fullwidth">
<div className="hero-body">
<div className="container">
<div className="columns is-centered">
<div className="column is-4-desktop">
<form onSubmit={Register} className="box">
<p className="has-text-centered">{msg}</p>
<div className="field mt-5">
<label className="label">Name</label>
<div className="controls">
<input type="text" className="input" placeholder="Name"
value={name} onChange={(e) => setName(e.target.value)} />
</div>
</div>
<div className="field mt-5">
<label className="label">Email</label>
<div className="controls">
<input type="text" className="input" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} />
</div>
</div>
<div className="field mt-5">
<label className="label">Password</label>
<div className="controls">
<input type="password" className="input" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} />
</div>
</div>
<div className="field mt-5">
<label className="label">Confirm Password</label>
<div className="controls">
<input type="password" className="input" placeholder="Confirm Password" value={confPassword} onChange={(e) => setConfPassword(e.target.value)} />
</div>
</div>
<div className="field mt-5">
<button className="button is-success is-fullwidth">Register</button>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
)
}
export default Register
I don't think need the '/' in the routes, and you can just navigate to "register" and "dashboard". Only use anchor tags when linking to external pages. For internal navigation use Link tags: <Link to="about">About</Link> or navigate() for buttons.
The snippet below is modified from my code. Before the user is signed in (not authorized), show your marketing pages and the sign-in / sign-up buttons. Show marketing page by default, and sign-in / sign-up buttons will navigate to "enter" or similar. The * catch-all route will catch this and show your sign-in/sign-up page. Also, if someone is coming from a bookmark the "*" route catches that and directs them to sign-up, which perhaps they bounce through and get to the intended destination.
Once user is authenticated, return the 'dashboard' routes.
Note how the dashboard routes are nested and there's a component that wraps them all (good for your navbar). You put <Outlet/> into that component and any nexted routes will render where the outlet is. Also, within those routes, navigation is relative. ie. you can navigate(jobId) and not navigate(`jobs/${jobId}`).
const unauthenticatedRoutes = (
<Routes>
<Route path="/" element={<MarketingWrapper />}>
<Route index element={<MarketingHome />
<Route path="*" element={<SignUpSignIn />} />
</Route>
</Routes>
)
const authenticatedRoutes = (
<Routes>
<Route path="/" element={<DashboardWrapper />}>
<Route index element={<Dashboard />
<Route path="*" element={<NoMatch />} />
<Route path="enter" element={<SignUpSignIn />} />
<Route path='signout' element={<SignOut signOut={signOut} />} />
<Route path="jobs" element={<JobsList jobId={jobId!} />}>
<Route path=":jobId" element={<JobPage />} />
<Route index element={<JobNew jobId={jobId!} />} />
</Route>
</Route>
</Routes>
)
return (
isAuthenticated ? authenticatedRoutes : unauthenticatedRoutes
)

Pass Data between classes components (React)

I'm new to react and web dev in general. I need to pass a few values from a class to another when switching pages. My routes and links are working just fine.
I've tried a lot of stuff but I can't manage to pass the data to the class that is going to render the next page on the website. I need your help please ! (feel free to give me advice about the code, it's probably pretty shitty lol)
App.jsx
import React, { Component } from 'react'
import { BrowserRouter, Route, Routes} from 'react-router-dom';
import './App.css'
import SignIn from './SignIn/SignIn';
import Register from './Register/Register';
import Informations from './Register/Informations/Informations';
import PageNotFound from './PageNotFound/error';
import Photos from './Register/Informations/Photos/Photos'
class App extends Component {
render() {
return <BrowserRouter>
<Routes>
<Route path='/' element={<Register/>} exact/>
<Route path='/register' element={<Register/>}/>
<Route path='/sign-in' element={<SignIn/>}/>
<Route path='/infos' element={<Informations/>}/>
<Route path='/photos' element={<Photos/>}/>
<Route path='*' element={<PageNotFound/>}/>
</Routes>
</BrowserRouter>
}
}
export default App
Register.jsx (starting point of the site, I want the data to go from this class to the next one)
import React, { Component } from 'react';
import { Link } from "react-router-dom";
import './Register.scss'
import axios from 'axios'
import test_img from '../blink_logo.svg';
class Register extends Component
{
constructor (props) {
super(props);
this.state =
{
email: '',
firstname: '',
lastname: '',
telephone: '',
password: ''
}
this.handleLogin = this.handleLogin.bind(this);
}
handleLogin () {
axios.post('http://localhost:3000/api/user/', {email: this.state.email,
firstname: this.state.firstname,
lastname: this.state.lastname,
telephone: this.state.telephone,
password: this.state.password})
.then((response) =>
{
console.log(response);
this.setState({username: response.data.username})
}).catch(error =>
{
console.log(error);
})
console.log("Email: " + this.state.email);
console.log("Firstname: " + this.state.firstname);
console.log("Lastname: " + this.state.lastname);
console.log("Telephone: " + this.state.telephone);
console.log("Password: " + this.state.password);
}
handleEmailChange = (e) =>{
this.setState({email: e.target.value});
}
handleFirstnameChange = (e) =>{
this.setState({firstname: e.target.value});
}
handleLastnameChange = (e) =>{
this.setState({lastname: e.target.value});
}
handleTelephoneChange = (e) => {
this.setState({telephone: e.target.value});
}
handlePasswordChange = (e) => {
this.setState({password: e.target.value});
}
render () {
return (
<div>
<div className='image'>
< img src={test_img} alt="Blink Logo"/>
</div>
<div className='base-container-register'>
<div className='header'>Rejoindre l'aventure !</div>
<div className='content'>
<div className='form'>
<div className='label'>Email</div>
<div className='form-group'>
<input type="text" name="email" placeholder="email" value={this.state.email} onChange={this.handleEmailChange} />
</div>
<div className='label'>Prénom</div>
<div className='form-group'>
<input type="text" name="firstname" placeholder="prénom" value={this.state.firstname} onChange={this.handleFirstnameChange} />
</div>
<div className='label'>Nom</div>
<div className='form-group'>
<input type="text" name="lastname" placeholder="nom" value={this.state.lastname} onChange={this.handleLastnameChange} />
</div>
<div className='label'>Mot de passe</div>
<div className='form-group'>
<input type="password" name="password" placeholder="mot de passe" value={this.state.password} onChange={this.handlePasswordChange}/>
</div>
<div className='label'>Numéro de téléphone</div>
<div className='form-group'>
<input type="telephone" name="telephone" placeholder="numéro de téléphone" value={this.state.telephone} onChange={this.handleTelephoneChange}/>
</div>
</div>
<div className='footer' >
<Link to='/infos' state={{email: this.state.email}}>
<button onClick={this.handleLogin} className='footer'>Se lancer</button>
</Link>
{/* <Link to='/infos' params={email: this.state.email}>
<button onClick={this.handleLogin} className='footer'>Se lancer</button>
</Link> */}
</div>
</div>
</div>
<div className='redirect-to-signin'> Vous avez déjà un compte ? <Link to='/sign-in'>Sign in</Link> </div>
</div>
);
}
}
export default Register
Informations.jsx (where I want to grab the data from Register)
class Informations extends Component
{
constructor (props) {
super(props);
this.state =
{
username: '',
workInfo: '',
webSite: '',
socialId: '',
file: null,
file_name: '...'
}
this.handleLogin = this.handleLogin.bind(this);
this.handlePhotoChange = this.handlePhotoChange.bind(this)
}
I'm using react-router-dom v6.
Thank you in advance for your help.
When calling your Informations class just pass the values you want to send, as parameters.
<Informations value1={4} value2={8} />
To get the data you are sending from inside the Informations class:
this.props.value1
With v6 of react-router-dom, we can pass props to our components using the following method:
<Route path='/infos' element={<Informations value1={4} value2={8} />}/>
However, you are suggesting that values from the Register component need to make their way over to the Informations component. The better method for passing those values around would be to take advantage of React Context.
Using Context, you could have some state defined in a custom context that you can set in Register, and read in Informations.

React router dom v5 Nested routes never matching

I have two main pages: LoginPage and HomePage. HomePage is main page with navbar and content. I'm trying to implement nested routing.
loginPage
HomePage
SchoolsPage
UserDetails
I prepared routing for them:
<Switch>
<ProtectedRoute
key="loginPage"
path={`${ROOT_PATHS.login}`}
component={AuthPageContainer}
shouldBeRedirected={authState === 'AUTHENTICATED'}
authenticationPath={`${ROOT_PATHS.version}`}
/>
<ProtectedRoute
key="homePage"
path={ROOT_PATHS.version}
component={HomePageContainer}
shouldBeRedirected={authState === 'NOT_AUTHENTICATED'}
authenticationPath={`${ROOT_PATHS.login}`}
/>
</Switch>
Home page:
export const HomePage: React.FC<HomePageProps> = ({ fetchUserInfo, user, isLoading, userLogout }): JSX.Element => {
useEffect(() => {
fetchUserInfo()
}, [fetchUserInfo])
return (
<LoadingContent isLoading={isLoading}>
<Navbars user={user} userLogout={userLogout} />
<Pages />
</LoadingContent>
)
}
An routings in Pages:
export const Pages: React.FC = (): JSX.Element => {
const { url, path } = useRouteMatch()
return (
<Switch>
<Route path={`${url}${ROOT_PATHS.userDetails}`} exact component={UserDetailsContainer} />
<Route path={`${url}${ROOT_PATHS.schools}`} exact component={SchoolListContainer} />
<Route path={`${path}`} exact>
{console.log('redirected')}
<Redirect to={`${url}${ROOT_PATHS.schools}`} />
</Route>
</Switch>
)
}
And paths:
export const ROOT_PATHS = {
login: '/login',
version: '/v1',
schools: '/schools',
userDetails: '/userDetails'
}
My index.tsx:
// axios should be configured before saga because saga makes requests on startup
configureAxios()
const store = configureStore()
export const DOMStructure: React.FC = (): JSX.Element => (
<Suspense fallback={<Spinner animation="border" role="status" />}>
<Provider store={store}>
<ConnectedRouter history={history}>
<CookiesProvider>
<AppContainer />
</CookiesProvider>
</ConnectedRouter>
</Provider>
</Suspense>
)
ReactDOM.render(<DOMStructure />, document.getElementById('root'))
It all works fine when navigating via links in navbar.
But when I refresh page, it never matches to any child-routes and is redirected to v1/schools (I checked that with console.log('redirected').
So I cannot render page under specific ur. When I put 'localhost:3000/v1/userDetails' in browser, it's always redirected to 'localhost:3000/v1/schools'.
Why is that? What i'm missing?

Props object is empty with history.push

I have an onClick handler used for filtering data that routes back to the same component, but with a different url. However, when this same components renders again, I cannot access props.location.
Lots of code has been left out for brevity.
Component:
import { useHistory } from 'react-router-dom';
const Dashboard = (props) => {
const history = useHistory();
console.log(props) // Empty
useEffect(() => {
console.log(props) // Empty
})
const handleFilter = argument => {
history.push('/filter'); // 'argument' left out to test routing, and to ensure props.location is accessible
}
return (
<button onClick={() => handleFilter('someArgumentHere')}>Filter</button>
)
}
Router:
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Redirect,
} from 'react-router-dom';
const PrivateRoute = ({ exact, path, component: Component }) => {
return (
<Route
exact={exact}
path={path}
render={props => (
<div>
<Navbar />
<Component {...props} />
</div>
)}
/>
);
};
<Router>
<Switch>
<PrivateRoute exact component={Dashboard} path="/" />
<PrivateRoute exact component={Dashboard} path="/filteredPriority" />
</Switch>
</Router>
When I click on handleFilter, the routing works. Meaning, my browser navigates from '/' to '/filteredPriority', and I see the same content, which is desired. But, since this is a filter, I want to access the url params via props.location, and it's empty. I don't know why.
Figured it out. Needed to wrap my Dashboard component in withRouter.