props.history.push() doesn't redirect - reactjs

props.history.push("/") doesn't redirect.
I did search for solution for this problem and can't find where is the problem and that drives me crazy.
index.js
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/css/bootstrap-theme.css';
import './index.css';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import { createBrowserHistory } from 'history';
import configureStore from './store/configureStore';
import App from './App';
//import registerServiceWorker from './registerServiceWorker';
// Create browser history to use in the Redux store
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
const history = createBrowserHistory({ basename: baseUrl });
// Get the application-wide store instance, prepopulating with state from the server where available.
const initialState = window.initialReduxState;
const store = configureStore(history, initialState);
//Render app on "root" <div> in index.html
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('root'));
//registerServiceWorker();
app.js
import React from 'react';
import { Route, Switch } from 'react-router';
import Home from './components/Home';
import Login from './components/Login/Login';
import Counter from './components/Counter';
import FetchData from './components/FetchData';
import { PrivateRoute } from './components/PrivateRoutes/PrivateRoute';
const App = (props) => {
return (
<Switch>
<Route path="/login" component={Login}/>
<PrivateRoute path="/" component={Home} />
<Route path='/counter' component={Counter} />
<Route path='/fetchdata/:startDateIndex?' component={FetchData} />
</Switch>
);
}
export default App;
Login.js
import React, { useState } from 'react';
import { withRouter, Redirect, history } from 'react-router-dom'
import { Form, Label, FormGroup, FormControl, Button } from 'react-bootstrap';
import Home from '../Home';
//Login user
function LoginUser(username, password, callback) {
console.log("Atemt to login..." + " " + username);
fetch('api/SampleData/Login', {
method: "POST",
body: JSON.stringify({
email: username,
password: password,
})
}).then(response => response.json())
.then(json =>callback(json))
}
function Login(props) {
var logged = false;
var data = { username: '', password: '', };
function getUsername(event) {
data.username = event.target.value;
console.log(data);
}
function getPassword(event) {
data.password = event.target.value;
console.log(data);
}
function requestCallback(res) {
if (res[0] === "connected") {
props.history.push('/');
console.log(props.history);
}
}
if (logged === true) {
return (<Redirect to="/" component={Home} />);
}
return (
<div style={{ position: 'absolute', left: '50%', top: '50%', transform: 'translate(-50%, -50%)' }}>
<Form >
<FormGroup controlId="formBasicEmail">
<Label>Email address</Label>
<FormControl type="email" placeholder="Enter email" onChange={getUsername} />
</FormGroup>
<FormGroup controlId="formBasicPassword">
<Label>Password</Label>
<FormControl type="password" placeholder="Password" onChange={getPassword} />
</FormGroup>
<Button variant="primary" onClick={() => LoginUser(data.username, data.password, requestCallback)} style={{ margin: '0 auto', display: 'block', width: '100px' }}>
Login
</Button>
</Form>
</div>
);
}
export default withRouter(Login);
As you can see Login component is wrapped with withRouter(Login). console.log(props) in Login.js file shows that history is passed to props.

The problem is that in your PrivateRoute you have a constant variable called userLogged which is initiated with the value false. If the variable is true, the defined component is rendered. If it is false, you redirect ot /login.
The value of userLogged is always false, so you always redirect to /login. I recommend you handle your logged-in state either in your parent component App or through the usage of a store from redux.

Use 'history' npm package
1)App.js
import React, { Component } from "react";
import { Route, Router } from "react-router-dom";
import { createBrowserHistory } from "history";
import Dashboard from "./components/dashboard.js ";
import Login from "./components/login.js";
import Profile from "./components/profile.js";
import PrivateRoute from "./privateRoute.js";
export const history = createBrowserHistory();
//refer 'history' for wanted component like profile.js
class App extends Component {
render() {
return (
<Router history={history}>
<div>
<PrivateRoute
path="/"
component={Dashboard}
exact
/>
<Route path="/login" component={Login} exact />
<PrivateRoute path="/profile" component={Profile} />
</div>
</Router>
);
}
}
export default App;
a)Once you login store some data in local Storage with key "user".
b)Based on this "user" object in localStorage routing will happen in prBivateRoute.js
c)If you want to log out clear localStorage it will navigate into loginPage
2)privateRoute.js
import React from "react";
import { Route, Redirect } from "react-router-dom";
const PrivateRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={props => {
if (localStorage.getItem("user")!= "") {
return <Component />;
}else {
return <Redirect to={{ pathname: "/login" }} />;
}
}}
/>
);
};
3)profile.js
import React, { Component } from "react";
import { history } from "./App.js";
class Profile extends Component {
goBack = () => {
history.push("/");
};
render() {
<button onClick={() => this.goBack()}> back to dashboard </button>;
}
}
export default Profile;

Related

[react]history.push changes url, but not rendering page

history.push in SearchInput.js changes url, but it does not re-render the page :(
I read some posts handling this issue, but all does not work.
SearchInput.js component is called at header.
app.js
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import AppContext from '../contexts/AppContext';
import Login from './Login';
import Main from './Main';
import { createBrowserHistory } from "history";
const history = createBrowserHistory();
const App = () => {
return (
<AppContext.Provider value={{}}>
<Router history={history}>
<Switch>
<Route path="/" component={Login} exact/>
<Route path="/contents" component={Main}/>
</Switch>
</Router>
</AppContext.Provider>
)
};
export default App;
main.js
import React, { useState, useEffect } from 'react';
import { Route } from 'react-router-dom';
import _ from "lodash";
import Header from './Share/Header';
import Footer from './Share/Footer';
import Alert from './Share/Alert';
import AlertContext from '../contexts/AlertContext';
import ShareDataContext from '../contexts/ShareDataContext';
import Search from './Search/Search';
import Targeting from './Targeting/Targeting';
import ModelHit from './ModelHit/ModelHit';
import {KEY_NAME} from '../consts/keyName';
const Main = (props) => {
const [alertState, setAlertState] = useState({
eventType: '',
eventMessage: '',
eventCount: 0,
});
return (
<AlertContext.Provider value={{ alertState, setAlertState }}>
<Header quarterRawData={quarterRawData}/>
<Alert />
<main className="blue-grey lighten-5">
<Route path="/contents/search" component={Search} exact />
<Route path="/contents/targeting" component={Targeting} exact />
<Route path="/contents/model-hit-list" component={ModelHit} exact />
</main>
<Footer />
</AlertContext.Provider>
);
};
export default Main;
SearchInput.js -> where call history.push
/* eslint-disable no-use-before-define */
import React from 'react';
import TextField from '#material-ui/core/TextField';
import Autocomplete from '#material-ui/lab/Autocomplete';
import {KEY_NAME} from '../../consts/keyName'
import { useHistory } from 'react-router-dom';
const SearchInput = (props) => {
// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top
const {options} = props;
const history = useHistory();
const searchHandler = (e, value) => {
if (value) {
history.push({
pathname: '/contents/search',
state: {
[KEY_NAME.SHARE_CODE]: value[KEY_NAME.SHARE_CODE],
},
})
}
}
return (
<Autocomplete
id="combo-box-demo"
options={options}
getOptionLabel={(option) => option['target']}
style={{ width: 400 }}
onChange={searchHandler}
renderInput={(params) => <TextField {...params} label="Search" variant="outlined"/>}
/>
);
}
export default SearchInput;

useLocation hook undefined on startup: TypeError: Cannot read property 'location' of undefined

I am writing a typescript React application that will log a user in and update the Dashboard component to display the user information, such as their access tokens and their name. I am using the useLocation hook to pass the state from the fetch request in the LoginCallback component to display it in the Dashboard component.
The problem I am having is that on startup the Dashboard component won't render as the state is undefined because it hasn't been passed by the LoginCallback component yet, giving me a TypeError: Cannot read property 'location' of undefined.
I'm wondering if there is a way of assigning a default value for the state.authenticated value, any help would be greatly appreciated! The code for the components are below:
Index.tsx:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import 'bootstrap/dist/css/bootstrap.css';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
App.tsx:
import React, {useState} from 'react';
import './App.css';
import {Navbar, Button} from 'react-bootstrap';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import LoginCallback from './components/LoginCallback';
import Dashboard from './components/Dashboard';
function App() {
const [name, setName] = useState('');
return (
<div>
<Navbar className='bg-dark'>
<Navbar.Brand className='text-white' href="#home">
<img
src={process.env.PUBLIC_URL+"/AgileInventoryLogo.png"}
width="159"
height="50"
className="d-inline-block align-top"
alt="React Bootstrap logo"
/>
</Navbar.Brand>
<Navbar.Toggle />
<Navbar.Collapse className="justify-content-end">
{name !== 'null' ?
<Navbar.Text className='text-white'>Not signed in: <Button className='text-white' href='/login' variant="outline-info">Sign In</Button></Navbar.Text> :
<Navbar.Text className='text-white'>Signed in as: <a className='text-white' href="#login">Mark Otto</a></Navbar.Text> }
</Navbar.Collapse>
</Navbar>
<Router>
<Switch>
<Route path="/login" component={() => { window.location.href = 'http://localhost:9090/oauth/authorize?response_type=code&client_id=client2&scope=read'; return null; }} />
<Route path="/oauth_callback" component={LoginCallback} />
<Route path="/dashboard" exact component={Dashboard} />
</Switch>
</Router>
<Dashboard/>
</div>
);
}
export default App;
Dashboard.js:
import React from 'react';
import {useLocation} from 'react-router-dom';
const Dashboard = () => {
const {state} = useLocation();
// COULD USE REDUX TO STORE USER INFO ACROSS COMPONENTS
return (
<div>
<p> Dashboard </p>
{ state.authenticated ?
<div>
<p>Authenticated</p>
<p> stored access token: {state.access_token} </p>
<p> refresh token: {state.refresh_token}</p>
<p> user: {state.user.user_name}</p>
</div>
:
<p>Not Authenticated</p>
}
</div>
);
}
export default Dashboard;
LoginCallback.tsx:
import React, { FC, useEffect } from "react";
import { RouteComponentProps, useHistory } from "react-router-dom";
import jwt_decode from "jwt-decode";
const LoginCallback: FC<RouteComponentProps> = ({ location }) => {
const history = useHistory();
useEffect(() => {
const code = (location.search.match(/code=([^&]+)/) || [])[1];
const qParams = [
`grant_type=authorization_code`,
`scope=read`,
`code=${code}`
].join("&");
fetch(`http://localhost:9090/oauth/token?${qParams}`, {
// credentials: "include",
method: 'POST',
headers: {
'Authorization': 'Basic Y2xpZW50MjpzZWNyZXQy'
}})
.then(res => res.json())
.then(response => {
var decode = jwt_decode(response.access_token);
history.push({pathname: '/', state: {
authenticated: true,
user: decode,
access_token: response.access_token,
token_type: response.token_type,
refresh_token: response.refresh_token,
expires_in: response.expires_in,
scope: response.scope
}});
console.log(response);
})
.catch(console.error);
}, []);
return null;
};
export default LoginCallback;
That's Because you used <Dashboard/> Component outside of a <Router> wrapper.
useLocation hook needs to be called in a component which is a child of <Router> component.
Edited
const Dashboard = () => {
const { state } = useLocation();
// COULD USE REDUX TO STORE USER INFO ACROSS COMPONENTS
return (
<div>
<p> Dashboard </p>
{ state && state.authenticated ? (
<div>
<p>Authenticated</p>
<p> stored access token: {state.access_token} </p>
<p> refresh token: {state.refresh_token}</p>
<p> user: {state.user.user_name}</p>
</div>
) : (
<p>Not Authenticated</p>
)}
</div>
);
};
I got the same error and it turned out to be multiple versions of react-router-dom.....incase anyone tries the above and it doesn't work. You can add an "overrides" section to the package.json. https://docs.npmjs.com/cli/v8/configuring-npm/package-json#overrides

Testing an Authenticated Route using Jest/Enzyme

I have a component that only displays when the route is authenticated.
The way authentication is setup is with react-router v4 as an HOC:
Root.js
import React from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import reduxThunk from 'redux-thunk';
import reducers from './App/reducerIndex';
const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
export default ({children, initialState={}})=>{
return(
<Provider store={createStoreWithMiddleware(reducers, initialState)}>
{children}
</Provider>
);
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import reduxThunk from 'redux-thunk';
import reducers from './App/reducerIndex';
import { AUTH_USER } from './App/actionTypes';
import App from './App/app';
import '../style/css/custom.scss';
const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = createStoreWithMiddleware(reducers);
const token = localStorage.getItem('token');
if (token) {
store.dispatch({ type: AUTH_USER });
}
import Root from './root';
ReactDOM.render(
<Root>
<App id={token}/>
</Root>,
document.querySelector('.app')
);
App.js
function PrivateRoute({ component: Component, auth, ...rest }) {
return (
<Route
{...rest}
render={props =>
auth ? (<div><Component {...props} /></div>) : (
<Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)
}
/>
);
}
function PublicRoute({ component: Component, auth, ...rest }) {
return <Route {...rest} render={props => (!auth ? <Component {...props} /> : <Redirect to="/dashboard" />)} />;
}
class App extends Component {
static contextTypes = {
router: PropTypes.object,
};
render() {
return (
<Router history={history} >
<Switch>
<PublicRoute auth={this.props.auth} path="/login" exact component={Login} />
<PrivateRoute auth={this.props.auth} path="/dashboard" exact component={Dashboard} />
</Switch>
</Router>
);
}
}
function mapStateToProps(state) {
return {
auth: state.auth.authenticated,
};
}
export default connect(
mapStateToProps,
null
)(App);
This is currently how the dashboard test is set up
dashboard test:
import React from 'react';
import { mount } from 'enzyme';
import Root from '../../../root';
import Header from './header';
import '../../../setupTests';
import Dashboard from './dashboard';
let wrapped;
beforeEach(() => {
const initialState = {
auth: true
};
wrapped = mount(
<Root initialState={initialState}>
<Dashboard />
</Root>
);
});
describe('Dashboard', () => {
console.log("The component is", wrapped)
// make our assertion and what we expect to happen
it('header component exists', () => {
expect(wrapped.contains(<Header />)).toBe(true)
});
it('renders without crashing', () => {
expect(component.contains(<Header />)).toBe(true)
});
});
Console.log keeps telling me the component is undefined. Would I have to wrap this around react-router? I an stumped.

URL change after sign in and don't show view(no reload, render page) - react

I have a login react class that after passed authentication and successfully authenticated to page should take me to admin page but in this situation it just change URL to admin page after successful sign in or sign out but doesn't show any new admin component at all. I am using react-router v4 and this app don't work with it quite well. Here is My components
Maybe there is any way to solve this problem. I used high order component withRouter that should solve this problem but it doesn't do anything. I have no errors in console.
Sign In
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {reduxForm, Field} from 'redux-form';
import {withRouter} from 'react-router-dom';
import * as actions from '../../actions/signin_user';
class SignIn extends Component {
handleFormSubmit({email, password}) {
this.props.signinUser({email, password});
}
renderAlert() {
if (this.props.errorMessage) {
return (
<div className="alert alert-danger">
<strong>Oops! Błąd logowania, kod błędu: {this.props.errorMessage}</strong>
</div>
)
}
}
render() {
const {handleSubmit, fields: {email, password}} = this.props;
return (
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<fieldset className="form-group">
<label>Adres Email:</label>
<Field {...email} name="email" type="text" className="form-control" component="input"/>
</fieldset>
<fieldset className="form-group">
<label>Hasło:</label>
<Field {...password} name="password" type="password" className="form-control" component="input"/>
</fieldset>
{this.renderAlert()}
<button action="submit" className="btn btn-primary">Zaloguj się</button>
</form>
)
}
}
function mapStateToProps(state) {
return {errorMessage: state.auth.error}
}
SignIn = reduxForm({form: 'SignIn', fields: ['email', 'password']})(SignIn);
export default withRouter(connect(mapStateToProps, actions)(SignIn));
Admin Page
/**
* Created by konraduciechowski on 26.08.2017.
*/
import React, {Component} from 'react';
import {Link} from 'react-router-dom';
class AdminPage extends Component {
render() {
return (
<div className="header-stick">
{/*IMPORTANT ADD FUNCTION TO NOT ABLE EVERYONE TO GET TO ADMIN PAGE
WHEN THEIR NOT ADMIN*/}
{/*NEED TO FIX ROUTES FOR ADMIN PAGE*/}
<ul className="admin-menu">
<li className="admin-menu-item">
<Link to="/panel+administracyjny/kalendarz">Kalendarz</Link>
</li>
<li className="admin-menu-item">
<Link to="/panel+administracyjny/blog">Blog</Link>
</li>
<li className="admin-menu-item">
<Link to="/panel+administracyjny/pracownik">Zarządzanie pracownikami</Link>
</li>
</ul>
</div>
)
}
}
export default AdminPage;
redux action
/**
* Created by konraduciechowski on 16.10.2017.
*/
import axios from 'axios';
import history from '../helpers/history';
import {AUTH_USER, AUTH_ERROR, UNAUTH_USER} from './types'
const ROOT_DEV_API = 'http://salonenface.dev/api/';
const ROOT_PROD_API = 'https://salonenface.pl/api/';
const ROOT_TEST_API = 'http://localhost:3090';
export function signinUser({email, password}) {
return function (dispatch) {
//Submit password n' email to server
axios.post(`${ROOT_TEST_API}/signin`, {email, password})
.then(response => {
// if request is good...
// Update state to indicate user is authenticated
dispatch({type: AUTH_USER});
// Save JWT token
localStorage.setItem('token', response.data.token);
// redirect to admin route section
history.push("/panel+administracyjny");
})
.catch((error) => {
///////////////////////////////////////////
// if request is bad...
// show error to user
dispatch(authError(error.response.status));
})
}
}
export function signupUser({email, password}) {
return function (dispatch) {
axios.post(`${ROOT_TEST_API}/signup`, {email, password})
.then(response => {
dispatch({type: AUTH_USER});
localStorage.setItem('token', response.data.token);
history.push('/panel+administracyjny');
})
.catch(error => dispatch(authError(error.response.data.error)))
}
}
export function authError(error) {
return {
type: AUTH_ERROR,
payload: error
}
}
export function signoutUser() {
localStorage.removeItem('token');
return {type: UNAUTH_USER}
}
EDIT:
Router.js
//React imports
import React, {Component} from 'react';
import {
BrowserRouter as Router,
Route,
Switch,
Redirect,
withRouter
} from 'react-router-dom';
//Redux imports
import {Provider} from 'react-redux';
import {createStore, applyMiddleware} from 'redux';
import promise from 'redux-promise';
import reduxThunk from 'redux-thunk';
//Css imports
import '../styles/App.css';
import '../styles/Admin.css';
import '../../node_modules/bootstrap/dist/css/bootstrap.css';
import '../../node_modules/font-awesome/css/font-awesome.css';
import '../../node_modules/react-big-calendar/lib/css/react-big-calendar.css';
//Page Components imports
import Header from './SiteElementsComponents/Header';
import About from './MainComponents/About';
import Blog from './MainComponents/Blog';
import BlogPost from './MainComponents/BlogPost';
import Contact from './MainComponents/Contact';
import Home from './MainComponents/Home';
import Order from './CalendarComponents/Order';
import Services from './MainComponents/Services';
import Login from './AuthComponents/Login';
import Register from './AuthComponents/Register';
import Footer from './SiteElementsComponents/Footer';
import OrderComplete from './CalendarComponents/OrderComplete';
import SignIn from './AuthComponents/SignIn';
import SignOut from './AuthComponents/SignOut';
import SignUp from './AuthComponents/SignUp';
//Admin Components imports
import AdminPage from './AuthComponents/AdminComponents/AdminPage';
import AdminCalendar from './AuthComponents/AdminComponents/AdminCalendar';
import AdminBlog from './AuthComponents/AdminComponents/AdminBlog';
import AdminBlogNewPost from './AuthComponents/AdminComponents/AdminBlogNewPost';
import AdminBlogShowPost from './AuthComponents/AdminComponents/AdminBlogShowPost';
import AdminEmployee from './AuthComponents/AdminComponents/AdminEmployee';
import AuthExample from './TestComponents/AuthExample';
//Redux reducers
import reducers from '../reducers';
//History
import history from '../helpers/history';
//Middleware
const createStoreWithMiddleware = applyMiddleware(promise, reduxThunk)(createStore);
const App = () => {
return (
<Provider store={createStoreWithMiddleware(reducers)}>
<div>
<div className="col-xs-12">
<Router history={history}>
<div>
<Header/>
<div className="container background-block">
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/o+nas" component={About}/>
<Route path="/aktualnosci" component={Blog}/>
<Route path="/post/:id" component={BlogPost}/>
<Route path="/kontakt" component={Contact}/>
<Route path="/uslugi" component={Services}/>
<Route path="/logowanie" component={Login}/>
<Route path="/signin" component={SignIn}/>
<Route path="/wylogowanie" component={SignOut}/>
<Route path="/signup" component={SignUp}/>
<Route path="/rejestracja" component={Register}/>
<Route path="/wizyta+zapisana" component={OrderComplete}/>
{/*Calendar options*/}
<Route path="/wizyta+zapisana" component={OrderComplete}/>
<Route path="/wizyta" component={Order}/>
</Switch>
{/*test options*/}
<Route exact path="/AuthExample" component={AuthExample}/>
{/*/!*admin options*!/*/}
<Switch>
<Route exact path="/panel+administracyjny" component={AdminPage}/>
<Route path="/panel+administracyjny/kalendarz" component={AdminCalendar}/>
{/*admin blog options*/}
<Route path="/panel+administracyjny/blog" component={AdminBlog}/>
<Route path="/panel+administracyjny/post/nowy" component={AdminBlogNewPost}/>
<Route path="/panel+administracyjny/pokaz+post/:id"
component={AdminBlogShowPost}/>
{/*admin employee options*/}
<Route path="/panel+administracyjny/pracownik" component={AdminEmployee}/>
</Switch>
</div>
</div>
</Router>
<Footer/>
</div>
</div>
</Provider>
);
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/Router';
import registerServiceWorker from './registerServiceWorker';
import './styles/index.css';
ReactDOM.render(<App />, document.getElementById('app'));
registerServiceWorker();
You can not pass a history to a <BrowserRouter>, as it creates its
own history object
If you are creating your own history Object use <Router> instead of <BrowserRouter> and then pass history object
Import { Router } from 'react-router-dom'
<Router history={history}> <Router/>
You can read more on react-router v.4

reactjs on page load ajax calls, console.log etc are called from accross components even if those components are not executed

I have 2 main components, Home and Login.
Login: logs the user in and redirects to home component
Home: does API call get some data.
At Login component in componentDidMount(), i have a console.log(). The console.log gets executed on both Login and Home component even that its only present on Login component.
At Home component in componentDidMount(), i have getData() method.
The getData() method gets executed on both Login and Home component even that its only present on Home component.
I am not sure if this is do to redux rehydrate. Is there a way to prevent actions unique to a specific component being executed at the different component?
store:
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import reducer from './reducer';
import homeReducer from './homeReducer';
import {autoRehydrate, persistStore} from 'redux-persist';
import localForage from 'localforage';
const store = createStore(
combineReducers({
reducer,
homeReducer
}),
{},
applyMiddleware(thunk, logger),
autoRehydrate()
);
persistStore(store, {storage: localForage})
export default store;
Login Component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { login, setLogingSuccess } from '../../actions/loginActions';
import {Header} from '../Shared/header';
import PropTypes from 'prop-types';
import cookies from 'universal-cookie';
class LoginForm extends Component {
constructor(props) {
super(props);
this.state = {
//...
};
}
componentDidMount(){
console.log('login component');
//...
}
componentWillReceiveProps(nextProps) {
//...
}
render() {
//...
return (
<div className="col-md-6 col-md-offset-3" onSubmit={this.onSubmit}>
<h3>Login</h3>
<form name="LoginForm">
<label htmlFor="email">Email:</label>
<input
className="form-control"
type="email"
name="email"
value={this.state.email}
onChange={e => this.setEmail(e)}
/>
<br />
<label>Password:</label>
<input
className="form-control"
type="password"
name="password"
onChange={e => this.setPassword(e)}
/>
<br />
//...
</form>
</div>
);
}
};
//...
}
LoginForm.propTypes = {
//...
};
const mapStateToProps = state => {
return {
//...
};
};
const mapDispatchToProps = dispatch => {
return {
//...
};
};
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
Home component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import axios from 'axios';
import Header from '../Shared/header';
import {
//...
} from '../../actions/homeActions';
class Home extends Component {
constructor(props) {
super(props);
this.state = {
//...
};
}
componentDidMount(){
this.getData();
}
componentWillReceiveProps(nextProps) {
//...
}
render() {
return (
<div className="col-md-6 col-md-offset-3">
<Header />
<div>
<h3>Home</h3>
</div>
</div>
);
}
getData = function(){
axios.get('/_/data/')
.then(success =>{
console.log(success);
})
.catch(error =>{
console.log(error)
});
};
}
Home.propTypes = {
//...
};
const mapStateToProps = state => {
return {
//...
};
};
const mapDispatchToProps = dispatch => {
return {
//..
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
index.js:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import store from './redux/store';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import LoginForm from './components/LoginForm/LoginForm';
import Home from './components/HomePage/home';
class App extends React.Component {
render() {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<Route />
<Switch>
<Route exact path={'/'} component={Home} />
<Route path={'/login'} component={LoginForm} />
<Route path="*" component={LoginForm} />
</Switch>
</div>
</BrowserRouter>
</Provider>
);
}
}
render(<App />, document.getElementById('root'));
I have discovered couple issues while improving the solution to this issue:
When i would re-hydrate auth values, they were not properly reset
when user logged out
Proper way to manage auth is through the router and not though the components. This prevents needless api calls or state changes.
I did some research and found the following guide: https://codepen.io/bradwestfall/project/editor/XWNWge?preview_height=50&open_file=src/app.js which i have used to adjust the auth issue
I have added a new component:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import PropTypes from 'prop-types';
import cookies from 'universal-cookie';
class AuthorizedRoute extends React.Component {
componentWillMount() {}
getAuth() {
const cookie = new cookies();
return cookie.get('auth');
}
render() {
const { component: Component, ...rest } = this.props;
return (
<Route
{...rest}
render={props => {
return this.getAuth() ? (
<Component {...props} />
) : (
<Redirect to="/login" />
);
}}
/>
);
}
}
AuthorizedRoute.propTypes = {
render: PropTypes.any,
component: PropTypes.any
};
export default AuthorizedRoute;
Updated index.js route switch statement to:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import store from './redux/store';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import LoginForm from './components/LoginForm/LoginForm';
import Home from './components/HomePage/home';
import AuthorizedRoute from './.../AuthorizedRoute';
class App extends React.Component {
render() {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<Route />
<Switch>
<AuthorizedRoute exact path={'/'} component={Home} />
<Route
exact
path={'/login'}
render={props => <LoginForm {...props} />}
/>
<Route path="*" render={props => <LoginForm {...props} />} />
</Switch>
</div>
</BrowserRouter>
</Provider>
);
}
}
render(<App />, document.getElementById('root'));
Reinitialized auth vars in login component:
componentDidMount() {
const cookie = new cookies();
const auth = cookie.get('auth');
if (auth) {
this.props.history.push('/');
} else {
if (this.props.isLoginSuccess) {
this.props.setLogingSuccess(false);
this.props.setLoggedUser(null);
}
}
}
componentWillReceiveProps(nextProps) {
const { isLoginSuccess } = this.props;
if (!isLoginSuccess && nextProps.isLoginSuccess) {
this.props.history.push('/');
}
}
Updated reducer REHYDRATE to only story needed info:
case REHYDRATE:
var incoming = action.payload.reducer;
if (incoming) {
return {
...state,
loggedUser: incoming.loggedUser,
isLoginSuccess: incoming.isLoginSuccess,
};
}
break;

Resources