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 },
}}
/>
)
}
/>
);
};
Related
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} />
...
);
};
I have a React ecommerce site that is currently integrated with Stripe. When a successful payment is submitted, the cart is emptied (managed in localStorage), however the Cart quantity in the Navbar isn't resetting to 0.
The cart quantity is being managed in state in the <App /> component, setQty. The stripe payment is submitted in the <PaymentForm> component which is nested 4 components deep:
<App /> > <Checkout /> > <PaymentForm /> > <CheckoutForm />
In CheckoutForm, I'm using setQty({quantity: 0}); which I thought would pass "0" up to <App /> and reset the Cart quantity, instead I get an error of "Unhandled Rejection (TypeError): setQty is not a function". Why is this? How can I get this to work? Also, is there an easier way of resetting the Cart without passing props through so many components?
A breakdown of each component so you can see how I'm passing setQty through each component:
App
import React, { useState, useEffect } from 'react';
import './App.css';
import Nav from './Nav';
import Shop from './Components/Shop';
import Info from './Components/Info';
import Cart from './Components/Cart';
import Item from './Components/Item';
import Checkout from './Components/CheckoutForm/Checkout';
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import { getQuantity } from './helpers/helperTools';
function App() {
const storageItems = JSON.parse(localStorage.getItem('product'));
const [qty, setQty] = useState({quantity: getQuantity(storageItems || [])});
console.log("Apppp", qty)
return (
<Router>
<div className="App">
<Nav qty={qty.quantity} />
<Route path="/" exact component={Shop} />
<Route path="/Info" component={Info} />
<Route path="/Cart/" render={(props) => <Cart {...props} setQty={setQty} />} />
<Route path="/Item/:item" component={Item} />
<Route path="/Checkout" component={Checkout} setQty={setQty} />
</div>
</Router>
)
}
export default App;
Checkout
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import {
Paper,
Stepper,
Step,
StepLabel,
Typography,
CircularProgress,
Divider,
Button,
} from '#material-ui/core';
import useStyles from './styles';
import AddressForm from './AddressForm';
import PaymentForm from './PaymentForm';
const steps = ['Shipping address', 'Payment details'];
function Checkout({setQty}) {
const classes = useStyles();
const [activeStep, setActiveStep] = useState(0);
const [shippingData, setShippingData] = useState({});
const nextStep = () => setActiveStep((prevActiveStep) => prevActiveStep + 1);
const backStep = () => setActiveStep((prevActiveStep) => prevActiveStep - 1);
const next = (data) => {
setShippingData(data);
nextStep();
};
const Form = () =>
activeStep === 0 ? (
<AddressForm next={next} />
) : (
<PaymentForm shippingData={shippingData} backStep={backStep} nextStep={nextStep} setQty={setQty} />
);
const Confirmation = () => <div>Confirmation</div>;
return (
<div>
<div className={classes.toolbar} />
<main className={classes.layout}>
<Paper className={classes.paper}>
<Typography variant='h4' align='center'>
Checkout
</Typography>
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map((step) => (
<Step key={step}>
<StepLabel>{step}</StepLabel>
</Step>
))}
</Stepper>
{activeStep === steps.length ? <Confirmation /> : <Form />}
</Paper>
</main>
</div>
);
}
export default Checkout;
PaymentForm AND CheckoutForm, both in the same file
import React, { useState } from 'react';
import { Typography, Button, Divider } from '#material-ui/core';
import {
Elements,
CardElement,
ElementsConsumer,
useStripe,
useElements,
} from '#stripe/react-stripe-js';
import { loadStripe } from '#stripe/stripe-js';
import axios from 'axios';
import { getTotal } from '../../helpers/helperTools';
import Review from './Review';
const stripePromise = loadStripe(
'pk_HIDDEN_FOR_NOW'
);
const CheckoutForm = ({ shippingData, backStep, nextStep, setQty }) => {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async (event) => {
event.preventDefault();
if (!stripe || !elements) {
return;
}
const storageItems = JSON.parse(localStorage.getItem('product'));
const products = storageItems || [];
const totalPrice = getTotal(products);
let productTitle = '';
products.map((item, index) => {
productTitle = `${productTitle} | ${item.title}`;
});
const cardElement = elements.getElement(CardElement);
const { error, source } = await stripe.createSource(cardElement);
console.log(error, source);
const order = await axios.post('http://localhost:7000/api/stripe/charge', {
amount: totalPrice * 100,
source: source.id,
receipt_email: shippingData.email,
title: productTitle,
customerName: `${shippingData.firstName} ${shippingData.lastName}`,
address: {
city: shippingData.City,
country: shippingData.shippingCountry,
line1: shippingData.address1,
postal_code: shippingData.ZIP,
state: shippingData.shippingState,
},
});
if (error) {
console.log('[error]', error);
} else {
console.log('[PaymentMethod]', order);
localStorage.setItem('product', JSON.stringify([]));
nextStep();
setQty({quantity: 0});
}
};
return (
<form onSubmit={handleSubmit}>
<CardElement
options={{
style: {
base: {
fontSize: '16px',
color: '#424770',
'::placeholder': {
color: '#aab7c4',
},
},
invalid: {
color: '#9e2146',
},
},
}}
/>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Button variant='outlined' onClick={backStep}>
Back
</Button>
<Button type='submit' variant='contained' disabled={!stripe} color='primary'>
Pay
</Button>
</div>
</form>
);
};
function PaymentForm({ shippingData, backStep, nextStep, setQty }) {
return (
<Elements stripe={stripePromise}>
<Review />
<br />
<br />
<CheckoutForm shippingData={shippingData} nextStep={nextStep} backStep={backStep} setQty={setQty} />
</Elements>
);
}
export default PaymentForm;
Screenshot of my file structure
In the App component, you need to pass setQty as below. Props that are mentioned in the Route component would not be transmitted to the component by itself, and we need to use the render function to pass props.
<Route path="/Checkout" render={(props) => <Checkout setQty={setQty} {...props}/>
I have small problem with React Router and redirecting. After i have created a admin protected route, I want the user to be redirected to "user dashboard" after login. But my issue is that redirect is not working.
All is happening like this:
My Navigation componente, but this one i think is okay:
import React from 'react';
import { Link, withRouter } from 'react-router-dom';
import { signOut, isAuthUser } from '../../../utils/utils';
const isActive = (history, path) => {
if (history.location.pathname === path) {
return { color: '#ff9900' };
} else {
return { color: '#ffffff' };
}
};
const Navigation = ({ history }) => {
return (
<nav>
<ul className='nav nav-tabs bg-primary'>
<li className='nav-item'>
<Link className='nav-link' style={isActive(history, '/')} to='/'>
Home
</Link>
<Link
className='nav-link'
style={isActive(history, '/user/dashboard')}
to='/user/dashboard'
>
Dashboard
</Link>
{!isAuthUser() && (
<div>
<Link
className='nav-link'
style={isActive(history, '/signup')}
to='/signup'
>
Signup
</Link>
<Link
className='nav-link'
style={isActive(history, '/signin')}
to='/signin'
>
Signin
</Link>
</div>
)}
{isAuthUser() && (
<Link
className='nav-link'
style={isActive(history, '/signout')}
onClick={() => signOut()}
to='/'
>
Sign Out
</Link>
)}
</li>
</ul>
</nav>
);
};
export default withRouter(Navigation);
My app.js with Routes
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import MainLayout from '../src/components/layout/MainLayout/MainLayout';
import Signup from './components/views/Signup/Signup';
import Signin from './components/views/Signin/Signin';
import Home from './components/views/Home/Home';
import PrivateRoute from './components/common/ProvateRoute/PrivateRoute';
import UserDashboard from './components/views/UserDashboard/UserDashboard';
function App() {
return (
<BrowserRouter>
<MainLayout>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/signin' component={Signin} />
<Route exact path='/signup' component={Signup} />
<PrivateRoute exact path='/user/dashboard' component={UserDashboard} />
</Switch>
</MainLayout>
</BrowserRouter>
);
}
export default App;
My utils.js with some helper functions
export const signOut = () => {
if (typeof window !== 'undefined') {
localStorage.removeItem('jwt');
return fetch('http://localhost:8000/api/signout', { method: 'GET' }).then(res => {
console.log('signout', res);
});
}
};
export const authenticateUser = data => {
if (typeof window !== 'undefined') {
localStorage.setItem('jwt', JSON.stringify(data));
}
};
//check if user is auth and there is jwt item in localstorage. menu render
export const isAuthUser = () => {
if (typeof window == 'undefined') {
return false;
}
if (localStorage.getItem('jwt')) {
return JSON.parse(localStorage.getItem('jwt'));
} else {
return false;
}
};
So those looks okay in my opinion, but still i decided to post those here.
As all things that are most related are in my tow files: UserDashboard and Signin
My Signin.js looks like this:
import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import axios from 'axios';
import Layout from '../../layout/Layout/Layout';
import { authenticateUser, isAuthUser } from '../../../utils/utils';
class Signin extends Component {
state = {
formData: {
email: '',
password: '',
},
userRedirect: false,
};
onChange = e => {
const { formData } = this.state;
//assign form data to new variable
let newFormData = { ...formData };
newFormData[e.target.name] = e.target.value;
this.setState({
formData: newFormData,
});
};
signIn = user => {
const config = {
headers: {
'Content-Type': 'application/json',
},
};
axios
.post('http://localhost:8000/api/signin', user, config)
.then(res => authenticateUser(res.data));
this.setState({
formData: { email: '', password: '' },
userRedirect: true,
});
};
onSubmit = e => {
const { password, email } = this.state.formData;
e.preventDefault();
this.signIn({ email, password });
};
signInForm = (email, password) => (
<form onSubmit={this.onSubmit}>
<div className='form-group'>
<label className='text-muted'>Email</label>
<input
type='email'
name='email'
value={email}
onChange={this.onChange}
className='form-control'
></input>
</div>
<div className='form-group'>
<label className='text-muted'>Password</label>
<input
type='password'
name='password'
minLength='6'
value={password}
onChange={this.onChange}
className='form-control'
></input>
</div>
<button className='btn btn-primary'>Submit</button>
</form>
);
redirecUser = () => {
const { userRedirect } = this.state;
const { user } = isAuthUser();
if (userRedirect === true) {
if (user && user.role === 1) {
return <Redirect to='/admin/dashboard' />;
} else {
return <Redirect to='/user/dashboard' />;
}
}
};
render() {
const { email, password } = this.state.formData;
return (
<Layout
title='Signin'
description='Login to your account'
className='container col-md-8 offset-md-2'
>
{this.signInForm(email, password)}
{this.redirecUser()}
</Layout>
);
}
}
export default Signin;
Here I am rendering all with signInForm and passing all data that i want with signIn(). From this i get user data: _id, email, password, role and token. This is sent to local storage.
Based on that what i get i want admin dashboard or user dashboard.
I have now olny user Dashboard
import React from 'react';
import { isAuthUser } from '../../../utils/utils';
import Layout from '../../layout/Layout/Layout';
const UserDashboard = () => {
const {
payload: {
user: { name, email, role },
},
} = isAuthUser();
return (
<Layout
title='User Dashboard'
description={`Wlecome ${name}`}
className='container col-md-8 offset-md-2'
>
<div className='card mb-5'>
<h3 className='card-header'>User information</h3>
<ul className='list-group'>
<li className='list-group-item'>{name}</li>
<li className='list-group-item'>{email}</li>
<li className='list-group-item'>
{role === 1 ? 'Admin' : 'Registered User'}
</li>
</ul>
</div>
<div className='card'>
<h3 className='card-header'>Purchase history</h3>
<ul className='list-group'>
<li className='list-group-item'>History</li>
</ul>
</div>
</Layout>
);
};
export default UserDashboard;
I have created PrivateRoute component based on documentation
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { isAuthUser } from '../../../utils/utils';
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
isAuthUser() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/signin', state: { from: props.location } }} />
)
}
/>
);
export default PrivateRoute;
I do get all data in local storage, but after signin user is not redirected
Thanks for any help
Since you are trying to redirect from a function in your Signin.js try using history api.
this.props.history.push('/admin/dashboard')
instead of
<Redirect to='/admin/dashboard' />;
I am working on the react project. Currently, I am struggling to render the nav again after sign in to show the links that are only visible for logged in users. The following code works fine, and also the redirect goes to "/".
The main issue is that the links for logged-in users only visible after a reload of the page.
Nav.js
import React, {Component} from "react";
import {Nav, Navbar} from 'react-bootstrap';
import { Auth } from 'aws-amplify'
import SignIn from "./Authentication/SignIn";
import SignUp from "./Authentication/SignUp";
import SignOut from "./Authentication/SignOut";
import {
BrowserRouter as Router,
withRouter,
Redirect,
Switch,
Route,
Link
} from "react-router-dom";
import Share from "./Share";
import Subscribe from "./Subscribe";
import Home from "./Home"
class PrivateRoute extends React.Component {
constructor (props) {
super(props);
this.state = {
loaded: false,
isAuthenticated: false
}
}
componentDidMount() {
this.authenticate()
this.unlisten = this.props.history.listen(() => {
Auth.currentAuthenticatedUser()
.then(user => console.log('user: ', user))
.catch(() => {
if (this.state.isAuthenticated) this.setState({ isAuthenticated: false })
})
});
}
componentWillUnmount() {
this.unlisten()
}
authenticate() {
Auth.currentAuthenticatedUser()
.then(() => {
this.setState({ loaded: true, isAuthenticated: true});
})
.catch(() => this.props.history.push('/signup'))
}
render() {
const { component: Component, ...rest } = this.props
const { loaded , isAuthenticated} = this.state
if (!loaded) return null
return (
<Route
{...rest}
render={props => {
return isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/signup",
}}
/>
)
}}
/>
)
}
}
PrivateRoute = withRouter(PrivateRoute)
class Routes extends React.Component {
constructor (props) {
super(props);
this.state = {
showItemInMenu: false
}
}
componentDidMount() {
Auth.currentAuthenticatedUser()
.then(() => { this.setState({showItemInMenu: true })})
.catch(() => { this.setState({showItemInMenu: false})});
}
render() {
const showItemInMenu = this.state.showItemInMenu
return (
<Router>
<Navbar bg="dark" variant="dark">
<Navbar.Brand as={Link} to="/">Todo</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav"/>
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="ml-auto">
<Nav.Link as={Link} to="/">Home</Nav.Link>
{showItemInMenu && <Nav.Link as={Link} to="/share" >Share</Nav.Link>}
{showItemInMenu && <Nav.Link as={Link} to="/subscribe" >Subscribe</Nav.Link> }
{showItemInMenu && <Nav.Link as={Link} to="/signout" >Logout</Nav.Link> }
<Nav.Link as={Link} to="/signup" >Registration</Nav.Link>
<Nav.Link as={Link} to="/signin" >Login</Nav.Link>
</Nav>
</Navbar.Collapse>
</Navbar>
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/signup' component={SignUp}/>
<Route path='/signin' component={SignIn}/>
<PrivateRoute path='/share' component={Share}/>
<PrivateRoute path='/subscribe' component={Subscribe}/>
<PrivateRoute path='/signout' component={SignOut}/>
</Switch>
</Router>
)
}
}
export default Routes
Signin.js
import React, { Component } from "react";
import { Form, Row, Col,Button, Alert,Container } from 'react-bootstrap';
import { Auth } from "aws-amplify";
import styled from 'styled-components';
import { Redirect } from 'react-router-dom'
class SignIn extends Component {
constructor (props) {
super(props);
this.state = {
username: '',
password: '',
message: '',
redirect: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSignIn = this.handleSignIn.bind(this);
}
handleChange (event) {
this.setState({ [event.target.name]: event.target.value });
}
handleSignIn (event){
event.preventDefault();
const username = this.state.username;
const password = this.state.password;
// You can pass an object which has the username, password and validationData which is sent to a PreAuthentication Lambda trigger
Auth.signIn({
username,
password
})
.then(user => console.log(user))
.then(() => { this.setState({ redirect: true })
})
.catch(err =>
this.setState({message: err.message})
);
};
renderRedirect = () => {
if (this.state.redirect) {
return <Redirect
to={{
pathname: "/",
state: { fromlogin: true }
}}
/>
}
}
render () {
const Title = styled.div`
margin-top: 10px;
margin-bottom: 15px;
`;
return (
<Container>
<Row>
<Col md={6}>
<Title>
<h3>Login Form</h3>
</Title>
<Form onSubmit={this.handleSignIn}>
<Form.Group>
<Form.Control type="text" name="username" placeholder="Username" onChange={this.handleChange} />
</Form.Group>
<Form.Group>
<Form.Control type="password" name="password" placeholder="Password" onChange={this.handleChange} />
</Form.Group>
<Form.Group>
<Button
variant="primary"
type="submit"
value="SIGN IN"
>LOGIN</Button>
</Form.Group>
</Form>
{this.state.message ?
<Alert variant="danger">{this.state.message}</Alert>
: <></>}
</Col>
</Row>
{this.renderRedirect()}
</Container>
);
}
}
export default SignIn
I was able to solve this using React JS context API and local storage for storing the required state globally so that it does not revert on refresh.
Scenario: A top navbar containing site title is to visible every time regardless of user being signed in or not. The username and a logout button to appear in the top navbar only for logged in users. A side menu bar visible only for signed in users.
Issue: The username only appears after a reload to the main page just like the links in the above question.
Solution:
Create a file to store user context
UserContext.js
let reducer = (user, newUser) => {
if (newUser === null) {
localStorage.removeItem("user");
return newUser;
}
return { ...user, ...newUser };
};
const userState = {
name: "",
email: "",
};
const localState = JSON.parse(localStorage.getItem("user"));
const AuthContext = createContext();
const AuthProvider = (props) =>{
const [user, setUser] = useReducer(reducer, localState || userState);
useEffect(() => {
localStorage.setItem("user", JSON.stringify(user));
}, [user]);
return(
<AuthContext.Provider value={{ user, setUser }}>
{props.children}
</AuthContext.Provider>
)
}
export { AuthContext, AuthProvider };
Now in your App.js file wrap this AuthProvider over the components where user context is intended to be accessed.
App.js
function App() {
return (
<AuthProvider>
<Router>
<NavBar />
<Switch>
<Route path="/signin" exact component={SignIn} />
<Route path="/signup" component={SignUp} />
<ProtectedRoute path="/home" component={Home} />
</Switch>
</Router>
</AuthProvider>
);
}
Now consume the context in the components that were wrapped with the context like the Home and Navbar component.
NavBar.js
export default function MiniDrawer() {
const {user, setUser} = useContext(AuthContext)
//set user context and local/global storage respective value to null on signout
const handleSignOut = async () =>
{
try
{
await Auth.signOut()
localStorage.removeItem("user");
setUser(null)
history.push("/signin")
}catch(err)
{
console.log(err.message)
alert('Could not sign out due to: ', err.message)
}
}
return (
<div className={classes.root}>
<CssBaseline />
<AppBar
position="fixed"
className={clsx(classes.appBar, {
[classes.appBarShift]: open,
})}
>
<Toolbar>
{ user && user.name &&
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
className={clsx(classes.menuButton, {
[classes.hide]: open,
})}
>
<MenuIcon />
</IconButton>
}
<Typography variant="h6" noWrap>
The Video Site
</Typography>
{ user && user.name &&
<div className={classes.userArea}>
<Typography >
Welcome, {user && user.name}
</Typography>
<ListItem button onClick={handleSignOut}>
<ListItemIcon>{<ExitToAppIcon /> }</ListItemIcon>
<ListItemText primary="Log out" />
</ListItem>
</div>
}
</Toolbar>
</AppBar>
{ user && user.name &&
<Drawer
variant="permanent"
className={clsx(classes.drawer, {
[classes.drawerOpen]: open,
[classes.drawerClose]: !open,
})}
classes={{
paper: clsx({
[classes.drawerOpen]: open,
[classes.drawerClose]: !open,
}),
}}
>
<div className={classes.toolbar}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === 'rtl' ? <ChevronRightIcon /> : <ChevronLeftIcon />}
</IconButton>
</div>
<Divider />
<List>
{
SideBarData.map( (item) => {
return(
<Link to={item.path}>
<Tooltip title={item.title} aria-label="add">
<ListItem button key={item.title} >
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.title} />
</ListItem>
</Tooltip>
</Link>
)
}
)
}
</List>
<Divider />
</Drawer>
}
<main className={classes.content}>
<div className={classes.toolbar} />
{/* <MediaUpload /> */}
</main>
</div>
);
}
Now, you have implemented context with local storage in order to maintain state even after refreshing.
Guidance source
I am trying to redirect a user after login to home page, the redirect happens (this I know because the url changes and I go to a new page), but the content of the page doesn't load. I keep on getting this error
Warning: You tried to redirect to the same route you're currently on: "/index"
Here's my code for Login.js:
import React, { Component } from 'react';
import '../App.css';
import 'whatwg-fetch';
import { connect } from 'react-redux';
import { loginUser } from './../store/actions/loginActions';
import { Redirect, withRouter } from 'react-router-dom';
class Login extends Component {
constructor(props) {
super(props);
this.state = {
Email: '', Password: '', isloggedin: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ [event.target.name]: event.target.value });
}
handleSubmit(event) {
event.preventDefault();
this.props.loginUser(this.state)
}
render() {
const { authError, token } = this.props;
if(token) return <Redirect to="/index" /> // I'm telling it to redirect if the state has a token
return (
<div className="Background">
<Card
title="Login"
style={{ width: 400 }}
>
<Form onSubmit={this.handleSubmit} className="login-form">
<Form.Item>
<Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} name="Email" type="text" onChange={this.handleChange} />
</Form.Item>
<Form.Item>
<Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} type="password" name="Password" onChange={this.handleChange} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" disable='notloggedin' className="login-form-button">
Log in
</Button>
</Form.Item>
</Form>
<div>
{ authError ? <p> { authError } </p> : null }
</div>
</Card>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
authError: state.auth.authError,
token: state.auth.token
}
}
const mapDispatchToProps = (dispatch) => {
return {
loginUser: (data) => dispatch(loginUser(data))
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps) (Login));
My action is:
export const loginUser = (data) => {
return (dispatch, getState) => {
fetch(url, {
credentials: 'include',
method: 'POST',
headers: headers,
body: JSON.stringify(data)})
.then(res => { if (res.ok) {
return res.json() }
else {
return Promise.reject({
status: res.status,
statusText: res.statusText
})
}
})
.then(function(response) {
dispatch({type: 'LOGIN_USER', value: response.AccessToken });
})
.catch((err) => {
dispatch({type: 'LOGIN_USER_FAILED', err });
})
}
};
and my App.js is
import React, { Component } from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import './App.css';
import Login from './components/Login';
import Home from './components/Home';
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<h3 className="App-logo"> App Load </h3>
</header>
<BrowserRouter>
<Switch>
<Route path="/" component={Login} />
<Route path="/index" component={Home} />
</Switch>
</BrowserRouter>
);}}
export default App;
Could anyone please let me know if they see any reason as to why the redirect is working but not loading the page components? I'll really appreciate any pointers.
try to wrap you whole App component into BrowserRouter
I shifted this <Route path="/" component={Login} /> to the bottom of my routes in App.js and it worked.