React history.push not working from the Home component - reactjs

I'm implementing authentication to my React project and I'm trying to have the Login form in my Home component. This is how my Home component looks with the Login component attached to it.
import Login from './Login'
import * as React from 'react';
import Box from "#material-ui/core/Box";
const Home = () => {
return (
<div>
<Box sx={{
width: 600,
height: 150,
backgroundColor: 'lightgrey',
borderRadius: 16
}}>
{<h1>Welcome to our Podcast App</h1>}
{<h2>Feel free to share your favorites or have fun listening to the ones available.</h2>}
</Box><br/>
< Login />
</div>
);
};
export default Home;
The problem is, I want to redirect my users to the podcasts page and, history.push is not working. This is how the Login component looks.
import React from "react";
import { connect } from "react-redux";
import { loginUser } from "../actions/index";
import Button from "#material-ui/core/Button";
import { Box } from "#material-ui/core";
class Login extends React.Component {
state = {
email: "",
password: "",
error: false
};
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value
});
};
handleSubmit = (event) => {
event.preventDefault();
const { email, password } = this.state;
this.props
.dispatchLoginUser({ email, password })
.then(() => this.props.history.push('/podcasts'))
.catch(() => this.setState({ error: true }));
};
render() {
return (
<Box sx={{ p: 2, border: '1px solid grey' }}>
<form onSubmit={this.handleSubmit} >
<h3>Log In</h3>
<p>{this.state.error && "Invalid email or password"}</p>
<fieldset>
<label htmlFor='email'>
Email:
</label>
<input
type='text'
name='email'
id='email'
onChange={this.handleChange}
value={this.state.email}
/>
</fieldset>
<fieldset>
<label htmlFor='password'>
Password:
</label>
<input
type='password'
name='password'
id='password'
onChange={this.handleChange}
value={this.state.password}
/>
</fieldset><br/>
<Button variant="contained" size="small" color="inherit" type='submit'>Log In</Button>
</form>
</Box>
);
}
}
const mapDispatchToProps = (dispatch) => {
return {
dispatchLoginUser: (credentials) => dispatch(loginUser(credentials))
};
};
export default connect(null, mapDispatchToProps)(Login);
If I try to log in from the Login page, it will work. But it doesn't work from the Home page. I also tried adding history to my project like another post suggested but it still doesn't work. Here's the code:
//history.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
//index.js
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
ReactDOM.render(
<Provider store={ store }>
<Router history={history} >
<App />
</Router>
</Provider>
//The routes in App.js
<Route exact path='/' component={Home}/>
<Route exact path='/signup' component={Signup} />
<Route exact path='/login' component={Login} />
Is it possible to make history.push work from the Home component? Any suggestion will be welcomed. Thank you all.

In react-router-dom version 6
useHistory() is replaced by useNavigate()
import {useNavigate} from 'react-router-dom';
const navigate = useNavigate();
navigate('/home')

Is it possible to make history.push work from the Home component?
Yes, absolutely it is. You just need to destructure it from the props object in the Home component. For this the Home component needs to actually have a defined/declared props object.
const Home = (props) => {
// access props.history
return (
...
<Login history={props.history} />
...
);
};
or
const Home = ({ history }) => {
// access history
return (
...
<Login history={history} />
...
);
};
Home is a function component, so it can also use the useHistory hook and pass it along.
const Home = () => {
const history = useHistory();
return (
...
<Login history={history} />
...
);
};

Related

Add redux actions to login form?

I transitioned from a previous React app to a new template. Issue is i am quite confused about how redux is setup and how i can implement authentication.
LoginForm
// validation functions
const required = value => (value === null ? 'Required' : undefined);
const email = value =>
value && !/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) ? 'Invalid email' : undefined;
const LinkBtn = React.forwardRef(function LinkBtn(props, ref) {
// eslint-disable-line
return <NavLink to={props.to} {...props} innerRef={ref} />; // eslint-disable-line
});
// eslint-disable-next-line
class LoginForm extends React.Component {
// state = {
// showPassword: false,
// };
constructor() {
super();
this.state = {
email: '',
password: '',
errors: {},
showPassword: false,
};
}
handleClickShowPassword = () => {
const { showPassword } = this.state;
this.setState({ showPassword: !showPassword });
};
handleMouseDownPassword = event => {
event.preventDefault();
};
onSubmit = e => {
e.preventDefault();
const userData = {
email: this.state.email,
password: this.state.password,
};
loginUser(userData);
};
render() {
console.log(this.props);
const { classes, handleSubmit, pristine, submitting, deco } = this.props;
const { showPassword } = this.state;
return (
<Fragment>
<Hidden mdUp>
<NavLink to="/" className={classNames(classes.brand, classes.outer)}>
<img src={logo} alt={brand.name} />
{brand.name}
</NavLink>
</Hidden>
<Paper className={classNames(classes.paperWrap, deco && classes.petal)}>
<Hidden smDown>
<div className={classes.topBar}>
<NavLink to="/" className={classes.brand}>
<img src={logo} alt={brand.name} />
{brand.name}
</NavLink>
<Button
size="small"
className={classes.buttonLink}
component={LinkBtn}
to="/register"
>
<Icon className={classes.icon}>arrow_forward</Icon>
Create new account
</Button>
</div>
</Hidden>
<Typography variant="h4" className={classes.title} gutterBottom>
Sign In
</Typography>
<Typography variant="caption" className={classes.subtitle} gutterBottom align="center">
Lorem ipsum dolor sit amet
</Typography>
<section className={classes.socmedLogin}>
<div className={classes.btnArea}>
<Button variant="outlined" size="small" className={classes.redBtn} type="button">
<AllInclusive className={classNames(classes.leftIcon, classes.iconSmall)} />
Socmed 1
</Button>
<Button variant="outlined" size="small" className={classes.blueBtn} type="button">
<Brightness5 className={classNames(classes.leftIcon, classes.iconSmall)} />
Socmed 2
</Button>
<Button variant="outlined" size="small" className={classes.cyanBtn} type="button">
<People className={classNames(classes.leftIcon, classes.iconSmall)} />
Socmed 3
</Button>
</div>
<ContentDivider content="Or sign in with email" />
</section>
<section className={classes.formWrap}>
<form onSubmit={handleSubmit}>
<div>
<FormControl className={classes.formControl}>
<Field
name="email"
component={TextFieldRedux}
placeholder="Your Email"
label="Your Email"
required
validate={[required, email]}
className={classes.field}
/>
</FormControl>
</div>
<div>
<FormControl className={classes.formControl}>
<Field
name="password"
component={TextFieldRedux}
type={showPassword ? 'text' : 'password'}
label="Your Password"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="Toggle password visibility"
onClick={this.handleClickShowPassword}
onMouseDown={this.handleMouseDownPassword}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}}
required
validate={required}
className={classes.field}
/>
</FormControl>
</div>
<div className={classes.optArea}>
<FormControlLabel
className={classes.label}
control={<Field name="checkbox" component={CheckboxRedux} />}
label="Remember"
/>
<Button
size="small"
component={LinkBtn}
to="/reset-password"
className={classes.buttonLink}
>
Forgot Password
</Button>
</div>
<div className={classes.btnArea}>
<Button variant="contained" color="primary" size="large" type="submit">
Continue
<ArrowForward
className={classNames(classes.rightIcon, classes.iconSmall)}
disabled={submitting || pristine}
/>
</Button>
</div>
</form>
</section>
</Paper>
</Fragment>
);
}
}
const mapDispatchToProps = dispatch => ({
init: bindActionCreators(loginUser, dispatch),
loginUser:
});
LoginForm.propTypes = {
classes: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
pristine: PropTypes.bool.isRequired,
submitting: PropTypes.bool.isRequired,
loginUser: PropTypes.func.isRequired,
deco: PropTypes.bool.isRequired,
};
const LoginFormReduxed = reduxForm({
form: 'immutableExample',
enableReinitialize: true,
})(LoginForm);
const reducerLogin = 'login';
const reducerUi = 'ui';
const FormInit = connect(
state => ({
force: state,
initialValues: state.getIn([reducerLogin, 'usersLogin']),
deco: state.getIn([reducerUi, 'decoration']),
}),
mapDispatchToProps,
)(LoginFormReduxed);
export default withStyles(styles)(FormInit);
login.js
import React from 'react';
import { Helmet } from 'react-helmet';
import brand from 'dan-api/dummy/brand';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import { LoginForm } from 'dan-components';
import styles from 'dan-components/Forms/user-jss';
class Login extends React.Component {
state = {
valueForm: [],
};
submitForm(values) {
const { valueForm } = this.state;
setTimeout(() => {
this.setState({ valueForm: values });
console.log(`You submitted:\n\n${valueForm}`);
window.location.href = '/app';
}, 500); // simulate server latency
}
render() {
const title = brand.name + ' - Login';
const description = brand.desc;
const { classes } = this.props;
return (
<div className={classes.root}>
<Helmet>
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
</Helmet>
<div className={classes.container}>
<div className={classes.userFormWrap}>
<LoginForm onSubmit={values => this.submitForm(values)} />
</div>
</div>
</div>
);
}
}
Login.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Login);
My authactions i am trying to add.
import axios from 'axios';
import jwt_decode from 'jwt-decode';
import setAuthToken from '../../utils/setAuthToken';
import { GET_ERRORS, SET_CURRENT_USER, USER_LOADING } from '../constants/authConstants';
// Login - get user token
export const loginUser = userData => dispatch => {
axios
.post('/api/total/users/login', userData)
.then(res => {
// Save to localStorage
// Set token to localStorage
const { token } = res.data;
localStorage.setItem('jwtToken', JSON.stringify(token));
// Set token to Auth header
setAuthToken(token);
// Decode token to get user data
const decoded = jwt_decode(token);
// Set current user
dispatch(setCurrentUser(decoded));
})
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data,
}),
);
};
// Set logged in user
export const setCurrentUser = decoded => {
return {
type: SET_CURRENT_USER,
payload: decoded,
};
};
// User loading
export const setUserLoading = () => {
return {
type: USER_LOADING,
};
};
// Log user out
export const logoutUser = history => dispatch => {
// Remove token from local storage
localStorage.removeItem('jwtTokenTeams');
// Remove auth header for future requests
setAuthToken(false);
// Set current user to empty object {} which will set isAuthenticated to false
dispatch(setCurrentUser({}));
history.push('/dashboard');
};
and the authreducer
import { Map, fromJS } from 'immutable';
import { INIT } from '../constants/reduxFormConstants';
import { SET_CURRENT_USER, USER_LOADING } from '../constants/authConstants';
const isEmpty = require('is-empty');
const initialState = {
usersLogin: Map({
isAuthenticated: false,
user: {},
loading: false,
remember: false,
}),
};
const initialImmutableState = fromJS(initialState);
export default function reducer(state = initialImmutableState, action = {}) {
switch (action.type) {
case INIT:
return state;
case SET_CURRENT_USER:
return {
...state,
isAuthenticated: !isEmpty(action.payload),
user: action.payload,
};
case USER_LOADING:
return {
...state,
loading: true,
};
default:
return state;
}
}
I'm having a really hard time understaning how i can make this work together.
adding app.js
/**
* app.js
*
* This is the entry file for the application, only setup and boilerplate
* code.
*/
// Needed for redux-saga es6 generator support
import '#babel/polyfill';
// Import all the third party stuff
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router/immutable';
import FontFaceObserver from 'fontfaceobserver';
import history from 'utils/history';
import 'sanitize.css/sanitize.css';
// Import root app
import App from 'containers/App';
import './styles/layout/base.scss';
// Import Language Provider
import LanguageProvider from 'containers/LanguageProvider';
// Load the favicon and the .htaccess file
import '!file-loader?name=[name].[ext]!../public/favicons/favicon.ico'; // eslint-disable-line
import 'file-loader?name=.htaccess!./.htaccess'; // eslint-disable-line
import configureStore from './redux/configureStore';
// Import i18n messages
import { translationMessages } from './i18n';
// Observe loading of Open Sans (to remove open sans, remove the <link> tag in
// the index.html file and this observer)
const openSansObserver = new FontFaceObserver('Open Sans', {});
// When Open Sans is loaded, add a font-family using Open Sans to the body
openSansObserver.load().then(() => {
document.body.classList.add('fontLoaded');
});
// Create redux store with history
const initialState = {};
const store = configureStore(initialState, history);
const MOUNT_NODE = document.getElementById('app');
const render = messages => {
ReactDOM.render(
<Provider store={store}>
<LanguageProvider messages={messages}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</LanguageProvider>
</Provider>,
MOUNT_NODE,
);
};
if (module.hot) {
// Hot reloadable React components and translation json files
// modules.hot.accept does not accept dynamic dependencies,
// have to be constants at compile-time
module.hot.accept(['./i18n', 'containers/App'], () => {
ReactDOM.unmountComponentAtNode(MOUNT_NODE);
render(translationMessages);
});
}
// Chunked polyfill for browsers without Intl support
if (!window.Intl) {
new Promise(resolve => {
resolve(import('intl'));
})
.then(() =>
Promise.all([import('intl/locale-data/jsonp/en.js'), import('intl/locale-data/jsonp/de.js')]),
) // eslint-disable-line prettier/prettier
.then(() => render(translationMessages))
.catch(err => {
throw err;
});
} else {
render(translationMessages);
}
// Install ServiceWorker and AppCache in the end since
// it's not most important operation and if main code fails,
// we do not want it installed
if (process.env.NODE_ENV === 'production') {
require('offline-plugin/runtime').install(); // eslint-disable-line global-require
}
the app component
import React from 'react';
import jwt_decode from 'jwt-decode';
import { Switch, Route } from 'react-router-dom';
import NotFound from 'containers/Pages/Standalone/NotFoundDedicated';
import store from '../../redux/configureStore';
import { setCurrentUser, logoutUser } from '../../redux/actions/authActions';
import setAuthToken from '../../utils/setAuthToken';
import Auth from './Auth';
import Application from './Application';
import ThemeWrapper, { AppContext } from './ThemeWrapper';
window.__MUI_USE_NEXT_TYPOGRAPHY_VARIANTS__ = true;
class App extends React.Component {
render() {
console.log(this.props);
return (
<ThemeWrapper>
<AppContext.Consumer>
{changeMode => (
<Switch>
<Route path="/" exact component={Auth} />
<Route
path="/app"
render={props => <Application {...props} changeMode={changeMode} />}
/>
<Route component={Auth} />
<Route component={NotFound} />
</Switch>
)}
</AppContext.Consumer>
</ThemeWrapper>
);
}
}
export default App;
the auth component
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Outer from '../Templates/Outer';
import {
Login,
LoginV2,
LoginV3,
Register,
RegisterV2,
RegisterV3,
ResetPassword,
LockScreen,
ComingSoon,
Maintenance,
NotFound,
} from '../pageListAsync';
class Auth extends React.Component {
render() {
return (
<Outer>
<Switch>
<Route path="/login" component={Login} />
{/* <Route path="/login-v2" component={LoginV2} />
<Route path="/login-v3" component={LoginV3} />
<Route path="/register" component={Register} />
<Route path="/register-v2" component={RegisterV2} />
<Route path="/register-v3" component={RegisterV3} /> */}
<Route path="/reset-password" component={ResetPassword} />
<Route path="/lock-screen" component={LockScreen} />
{/* <Route path="/maintenance" component={Maintenance} />
<Route path="/coming-soon" component={ComingSoon} /> */}
<Route component={NotFound} />
</Switch>
</Outer>
);
}
}
export default Auth;
To maintain authentication using PLain redux is not quite possible because when ever you reload, the store get refreshed . However, redux has a functionality called Persisted store
Persisted Store store the data in memory and will not be refreshed with page reload or anything like that.
You can check this Link
Update with out persisted store:
In that case, Get the IsLoggedin state from store.
In App component
const App = () => {
const isLoggedIn = localStorage.getItem("jwtToken") !== null ? true: false
return (
<Router>
<Switch>
<PrivateRoute isLoggedIn={isLoggedIn} path="/dashboard">
<Dashboard />
</PrivateRoute>
<Route path="/login">
<Login authToken={authToken} />
</Route>
</Switch>
</Router>
);
and then in private route component:
const PrivateRoute = (props) => {
const { children, IsLoggedin, ...rest } = props;
return (
<Route
{...rest}
render={({ location }) =>
IsLoggedin ? (
children
) : (
<Redirect
to={{
pathname: '/login',
state: { from: location },
}}
/>
)
}
/>
);
};

problem with router and privaterouter / history

Hello I have a problem redirecting to a page doing a verification on a privaterouter
Unhandled Rejection (TypeError): Cannot read property 'push' of
undefined
on this line:
this.props.history.push ("/ home");
my component:
import React, { Component } from 'react';
import api from '../services/api';
import { withRouter } from 'react-router';
class LoginForm extends Component {
constructor(props){
super(props);
this.state = {
login:'',
password:'',
};
this.onSubmit = this.onSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
async onSubmit(e){
e.preventDefault();
const {login, password } = this.state;
const response = await api.post('/login', { login,password });
const user = response.data.user.login;
const {jwt} = response.data;
localStorage.setItem('token', jwt);
localStorage.setItem('user', user);
this.props.history.push("/home");
}
onChange(e){
this.setState({[e.target.name]: e.target.value});
}
render() {
const { errors, login, password, isLoading } = this.state;
return (
<form onSubmit={this.onSubmit}>
<label htmlFor="login">Login</label>
<input type="text" name="login" id="login" value={login} onChange={(e) => this.onChange(e)} placeholder="Informe seu login" />
<label htmlFor="password">Senha</label>
<input type="password" name="password" id="password" value={password} onChange={(e) => this.onChange(e)} placeholder="Informe sua senha"/>
<button className="btnEnt" type="submit">Entrar</button>
</form>
)
}
}
export default withRouter (LoginForm);
my router:
import React from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import Login from './pages/login/index';
import DashBoard from './pages/dashboard/index';
import PrivateRoute from './auth';
export default function Routes(){
return(
<BrowserRouter>
<div>
<Switch>
<Route path="/" exact component = {Login}/>
<PrivateRoute path="/home" component = {DashBoard}/>
</Switch>
</div>
</BrowserRouter>
);
}
my private route or auth router:
import React from 'react';
import { Route, Redirect} from 'react-router-dom';
const isAuth = () => {
console.log('a');
if(localStorage.getItem('token') !== null) {
console.log('true')
return true;
}
return false;
};
const PrivateRoute = ({component: Component, ...rest}) => {
return (
<Route
{...rest}
render={props =>
isAuth() ? (
<Component {...props} />
): (
<Redirect
to={{
pathname: '/',
state: {message: 'Usuário não autorizado'}
}}
/>
)}
/>
);
}
export default PrivateRoute;
I basically have my router and I also check if the user is allowed to enter this page, but I'm having trouble making it work.
Well, I read your code and here is my answer
You just need import withRouter from react-router-dom and not from react-router ;)
import { withRouter } from "react-router-dom";
And use it like
export default withRouter(LoginForm);

Redux not Working With React Router || Change Url not View || tried withRouter But not statisfy use on correct Component

This is My App.js where all the Routes define Under Router. It's work fine when i jump from one Link to other in those component that are not using redux. but when i click on Redux connected component it's render component but then when i click on any other Link they just change Url Not view.
This is App js File:-
import React, { useEffect, Fragment } from "react";
import { Router, Route, Switch } from "react-router-dom";
import history from "./history";
import Navbar from "./components/layouts/Navbar";
import Landing from "./components/layouts/Landing";
import Profiles from "./components/profiles/Profiles";
import Login from "./components/auth/Login";
import Register from "./components/auth/Register";
import { loadUser } from "./actions/auth";
import { useDispatch } from "react-redux";
const App = () => {
const dispatch = useDispatch(() => loadUser());
useEffect(() => {
dispatch(loadUser());
}, [dispatch]);
return (
<Router history={history}>
<Navbar />
<Route exact path='/' component={Landing} />
<section className='container'>
<Alert />
<Switch>
<Route exact path='/register' component={Register} />
<Route exact path='/login' component={Login} />
<Route path='/profiles' component={Profiles} />
</Switch>
</section>
</Router>
);
};
export default App;
Both Register And LogIn Workimg well when navigating through each other but when I jump to component that using redux profiles, it loads and work but after that when i want to jump from profiles to Register login they just change url not view.
this is My profiles file that using redux and creating issue.
import React, { Fragment, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getProfiles } from "../../actions/profile";
import Spinner from "../layouts/Spinner";
import ProfileItems from "./ProfileItems";
import { withRouter } from "react-router-dom";
const Profiles = () => {
const profile = useSelector(state => state.profile);
const { profiles, loading } = profile;
const dispatch = useDispatch(() => getProfiles());
useEffect(() => dispatch(getProfiles()), [dispatch]);
return (
<Fragment>
{loading ? (
<Spinner />
) : (
<Fragment>
<h1 className='large text-primary'>Developers</h1>
<p className='lead'>
<i className='fab fa-connectdevelop'></i> Browse and Connect With
Developers...
</p>
<div className='profiles'>
{profiles.length > 0 ? (
profiles.map(profile => (
<ProfileItems key={profile._id} profile={profile} />
))
) : (
<h4>profile not Found !!!...</h4>
)}
</div>
</Fragment>
)}
</Fragment>
);
};
export default withRouter(Profiles);
And These are My Login And Register component that are working well when navigate to each other. when they go to profiles after that when i click on link of them they just change urls in address bar but not changing view. It's Login page Register is similar to this..
import React, { Fragment, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { login } from "../../actions/auth";
import { Link, Redirect } from "react-router-dom";
const Login = () => {
const dispatch = useDispatch(() => login());
const isAuthenticated = useSelector(state
=>state.auth.isAuthenticated);
const [formData, setFormData] = useState({
email: "",
password: ""
});
const { email, password } = formData;
const onChange = e => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
};
const onSubmit = e => {
e.preventDefault();
dispatch(login(email, password));
};
if (isAuthenticated) {
return <Redirect to='/dashboard' />;
}
return (
<Fragment>
<h1 className='large text-primary'>Sign In</h1>
<p className='lead'>
<i className='fas fa-user'>Sign In Your Account!!!</i>
</p>
<form onSubmit={e => onSubmit(e)} className='form'>
<div className='form-group'>
<input
type='email'
name='email'
placeholder='Enter Your Email'
value={email}
onChange={e => onChange(e)}
/>
</div>
<div className='form-group'>
<input
type='password'
name='password'
placeholder='Enter Your Password'
value={password}
onChange={e => onChange(e)}
/>
</div>
<div className='form-group'>
<input type='submit' value='LogIn' className='btn btn-primary' />
</div>
</form>
<p className='my-1'>
Don't have an Account <Link to='/register'>Sign Up</Link>
</p>
</Fragment>
);
};
export default Login;
I searched this alot and mostly got ans use withRouter I tried that one as u can see but still not working or maybe i am not using withRouter on correct component.
I'll do Provide any other information that you need to know about my code if you want and I am using react-redux hooks instead of using connect
I had faced the same problem in the past.
At that time I solve this issue with connected-react-router.
this is how to use the connected-react-router.
how to use connected-react-router

How to do both inclusive and exclusive routing?

I need to be able to display the component on the home path "/" while I have "/signIn" modal up. However, after my user is signed in, I would like to replace the "/" component with the "/internal" component. I know how to use to render components exclusively, but I need a solution where I am rendering both inclusively and exclusively. My signIn modal must appear above the "/" component, and after the user is signed in I need the "/" component to be switched with the "/internal" component. I am only being able to do one or the other, is there a way to use both inclusive and exclusive routing in React-Router 4? Thank you.
What I tried:
<Route path="/" component={ExternalContainer} />
<Route path="/signIn" component={SignInModal} />
<Switch>
<Route path="/" component={ExternalContainer} />
<Route path="/internal" component={InternalContainer} />
</Switch>
What I have now but does not work:
<Route path="/" component={ExternalContainer} />
<Route path="/signIn" component={SignInModal} />
<Route path="/internal" component={InternalContainer} />
I think you are overcomplicating your routing structure. In theory, if you want to use a modal for your SignInModal component, then it does not need its own Route. You can just toggle its display within your ExternalContainer component. Then upon filling out the form and clicking the submit button in your SignInModal, just redirect to the InternalContainer.
See a very basic integration like this: https://codesandbox.io/s/old-snowflake-djbc3
Working code:
index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route } from "react-router-dom";
import ExternalContainer from "./ExternalContainer";
import InternalContainer from "./InternalContainer";
import "./styles.css";
function App() {
return (
<BrowserRouter>
<Route path="/" component={ExternalContainer} exact />
<Route path="/internal" component={InternalContainer} />
</BrowserRouter>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
ExternalContainer.js
import React from "react";
import SignInModal from "./SignInModal";
class ExternalContainer extends React.Component {
state = {
modalOpen: false
};
toggleModal = () => {
this.setState({
modalOpen: !this.state.modalOpen
});
};
render() {
return (
<div>
<h4>Welcome to the App</h4>
<button onClick={this.toggleModal}>Sign In</button>
<SignInModal
open={this.state.modalOpen}
toggleModal={this.toggleModal}
/>
</div>
);
}
}
export default ExternalContainer;
SignInModal.js
import React from "react";
import Modal from "react-modal";
import { withRouter } from "react-router-dom";
class SignInModal extends React.Component {
state = {
username: "",
password: ""
};
handleOnChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleOnSubmit = e => {
e.preventDefault();
const { username, password } = this.state;
if (username && password) {
this.props.history.push("/internal");
}
};
render() {
return (
<Modal
isOpen={this.props.open}
onRequestClose={this.props.toggleModal}
style={{
overlay: {
background: "rgba(0, 0, 0, 0.3)"
},
content: {
background: "white",
top: "45%",
left: "50%",
right: "auto",
bottom: "auto",
transform: "translate(-50%,-50%)",
width: "200px",
textAlign: "center"
}
}}
ariaHideApp={false}
>
<form onSubmit={this.handleOnSubmit}>
<div>
<label>Name</label>{" "}
<input name="username" onChange={this.handleOnChange} />
</div>
<div>
<label>Password</label>{" "}
<input name="password" onChange={this.handleOnChange} />
</div>
<button type="submit">Sign In</button>
</form>
</Modal>
);
}
}
export default withRouter(SignInModal);
InternalContainer.js
import React from "react";
const InternalContainer = () => {
return (
<div>
<h4>You have reached the internal container</h4>
</div>
);
};
export default InternalContainer;

ReactJS - Redirect to private route after state change

I have setup a login route in Express, which when used, it will set the global state of my application (using Context API), to include the users ID and a token, generated by JSONWebToken.
To be able to load todo items for the user, you need to have a valid token, which works when I'm sending it in the headers while doing the API request. The state is also being updated when doing this with Axios, but the problem is that I don't know how (or the best way for how) to forward to the protected route.
Here is my App.js, which includes the routes for my Welcome and Todo components. Inside of Welcome, there is a Login component, which updates the AppContext to contain the user ID and jsonwebtoken.
import React, { useContext } from 'react'
import { BrowserRouter as Router, Route, Redirect } from 'react-router-dom'
import { Switch } from 'react-router'
import TodoApp from './components/TodoApp/TodoApp'
import Welcome from './components/Welcome/Welcome'
import TodoProvider from './components/TodoApp/TodoContext'
import { AppContext } from './AppContext'
export default function App () {
const context = useContext(AppContext)
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
context.loggedIn ? <Component {...props} /> : <Redirect to="/" />
}
/>
)
return (
<Router>
<Switch>
<Route exact path="/" component={Welcome} />
<TodoProvider>
<PrivateRoute exact path="/todo" component={TodoApp} />
</TodoProvider>
</Switch>
</Router>
)
}
Notice the context.loggedIn property it checks for, which you can see how it's changed in the last code block of this post.
Here is my Login component which is inside of a simple Welcome component
import React, { useState, useContext } from 'react'
import { AppContext } from '../../../AppContext'
export default function Login (props) {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const context = useContext(AppContext)
const handleSubmit = async e => {
e.preventDefault()
try {
// Method which logs in using the /api/login route, and returns users ID and token to the global state (context)
await context.handleLogin(email, password)
} catch {
throw Error('Login error')
}
}
return (
<form handleSubmit={handleSubmit}>
<div className="input-wrapper">
<label htmlFor="email" />
<input
type="text"
id="login_email"
placeholder="email"
value={email}
onChange={e => setEmail(e.target.value)}
/>
</div>
<div className="input-wrapper">
<label htmlFor="password" />
<input
type="password"
id="login_password"
placeholder="password"
value={password}
onChange={e => setPassword(e.target.value)}
/>
</div>
<div className="input-wrapper">
<button
type="submit"
onClick={handleSubmit}
>
Log in
</button>
</div>
</form>
)
}
And finally, the method context.handleLogin which updates the state to contain the users ID and token. Taken from AppContext.js
handleLogin = (email, password) => {
axios
.post('http://localhost:4000/api/login/', {
email,
password
})
.then(response => {
this.setState({
loggedIn: response.httpStatus === 200,
userID: response.data.data.user._id,
token: response.data.data.token
})
})
.catch(err => {
this.setState({
httpStatus: err.response.status,
loginError: 'Incorrect email/password'
})
})
}
I'm new at React, so any help would be greatly appreciated. Thanks in advance!
I solved it by using withRouter() from react-router, and then pushing the history to the Private route.
Read more about it here:
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md

Resources