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.
Related
I have a project structure like that:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import {store, persistor} from './helpers';
import { Main } from './main';
import { PersistGate } from 'redux-persist/integration/react';
import { InitIDB } from './helpers/InitIDB';
require('./bootstrap');
InitIDB();
render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Main />
</PersistGate>
</Provider>,
document.getElementById('root')
);
This is entry point, and then the Main component:
import React from 'react';
import { Route, Switch, Router } from 'react-router-dom';
import { connect } from 'react-redux';
import { history } from '../helpers';
import {PrivateRoute} from "../components";
import { ProjectPage } from '../forms/project';
import { ProfilePage } from '../forms/profile';
import { Login } from '../forms/login';
class Main extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<React.Fragment>
<Router history={history}>
<Switch>
<Route exact path="/login" component={Login} />
<PrivateRoute exact path="/com/projects" component={ProjectPage} />
<PrivateRoute exact path="/com/profiles" component={ProfilePage} />
</Switch>
</Router>
</React.Fragment>
);
}
}
function mapState(state) {
const { alert } = state;
return { alert };
}
const actionCreators = {
clearAlerts: function() {}
}
const connectedApp = connect(mapState, actionCreators)(Main);
export { connectedApp as Main };
//PrivateRoute
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import {LayoutX} from '../forms/layout';
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
sessionStorage.getItem('user')
? <LayoutX><Component {...props} /></LayoutX>
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)} />
)
I want when user access private route, before that the program will send a request to server and it will return a response decide user can be access this page or not. I try to send a request with axios in PrivateRoute but it is async request and I can not get response before render. What should I do?
Your PrivateRoute should block render when waiting for the authentication status from API is returned.
You can take a look at a simple implementation of PrivateRoute I wrote in this codesandbox.
trying my hands on React/Redux following the tutorial on Part 3: https://tighten.co/blog/react-101-routing-and-auth.
Part 1 and 2 has been awesome until part 3 where I bumped into the error in the title. I'm pretty sure I've been following the tutorial fine up to this point.
Any help is much appreciated. Thanks in advance SO.
The above error occurred in the <ConnectedRouter> component:
index.js:1446
in ConnectedRouter (at App.js:39)
in App (created by Context.Consumer)
in Connect(App) (at src/index.js:11)
in Provider (at src/index.js:10)
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './containers/App';
import { Provider } from 'react-redux';
import { configureStore } from './store/configureStore';
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
)
App.js
import React from 'react';
import { ConnectedRouter } from 'react-router-redux';
import { Route, Redirect } from 'react-router-dom'
import { history } from './../store/configureStore';
import { connect } from 'react-redux';
import Header from '../containers/Header';
import Home from '../containers/Home';
import Signup from '../containers/Signup';
import Login from '../containers/Login';
import Favorites from '../containers/Favorites';
const PrivateRoute = ({component: Component, authenticated, ...props}) => {
return (
<Route
{...props}
render={(props) => authenticated === true
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
/>
);
};
const PublicRoute = ({component: Component, authenticated, ...props}) => {
return (
<Route
{...props}
render={(props) => authenticated === false
? <Component {...props} />
: <Redirect to='/favorites' />}
/>
);
};
class App extends React.Component {
render() {
return (
<ConnectedRouter history={history}>
<div>
<Header />
<div className="container">
<Route exact path="/" component={ Home }/>
<PublicRoute authenticated={this.props.authenticated } path="/signup" component={ Signup } />
<PublicRoute authenticated={this.props.authenticated } path="/login" component={ Login } />
<PrivateRoute authenticated={this.props.authenticated } path="/favorites" component={ Favorites } />
</div>
</div>
</ConnectedRouter>
);
}
}
const mapStateToProps = (state) => {
return { authenticated: state.auth.authenticated };
};
export default connect(mapStateToProps)(App);
ConfigureStore.js
import { createStore, compose, applyMiddleware } from 'redux';
import ReduxPromise from 'redux-promise';
import rootReducer from '../reducers';
import createHistory from 'history/createBrowserHistory';
import { routerMiddleware } from 'react-router-redux';
export const history = createHistory();
export function configureStore(initialState) {
const store = createStore(
rootReducer,
initialState,
compose (
applyMiddleware(ReduxPromise, routerMiddleware(history)),
window.devToolsExtension ? window.devToolsExtension() : f => f
)
//window.devToolsExtension ? window.devToolsExtension() : undefined
);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers').default;
store.replaceReducer(nextRootReducer);
});
}
return store;
}
The package react-router-redux is deprecated.
Use connected-react-router instead.
Solved it. Reverted to an older but compatible npm package of react-redux, react-router-dom/redux, redux and redux-promise at the time the project was published. Non-issue for now. Not sure what has changed. Will look into it again.
My react app is throwing the following error and as I have only a couple of weeks in react and even less in redux and I don't know how to overpass it. I tried different answers from the web but didn't manage to make it work. Maybe some of you can help.
The error:
Passing redux store in props has been removed and does not do anything. To use a custom Redux store for specific components, create a custom React context with React.createContext(), and pass the context object to React-Redux's Provider and specific components like: . You may also pass a {context : MyContext} option to connect
The code looks like this
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css'
import 'font-awesome/css/font-awesome.css'
import 'bootstrap-social/bootstrap-social.css'
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();
App.js
import React, { Component } from 'react';
import Main from './components/MainComponent';
import './App.css';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { ConfigureStore } from './redux/configureStore';
const store = ConfigureStore();
class App extends Component {
render() {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<Main />
</div>
</BrowserRouter>
</Provider>
);
}
}
export default App;
Reducer.js:
import { DISHES } from '../shared/dishes';
import { COMMENTS } from '../shared/comments';
import { PROMOTIONS } from '../shared/promotions';
import { LEADERS } from '../shared/leaders';
export const initialState = {
dishes: DISHES,
comments: COMMENTS,
promotions: PROMOTIONS,
leaders: LEADERS
};
export const Reducer = (state = initialState, action) => {
return state;
};
configureStore.js
import { createStore } from 'redux';
import { Reducer, initialState } from './reducer';
export const ConfigureStore = () => {
const store = createStore(
Reducer, // reducer
initialState, // our initialState
);
return store;
}
MainComponent.js
import React, { Component } from 'react';
import Menu from './MenuComponent';
import Header from './HeaderComponent'
import Footer from './FooterComponent'
import DishDetail from './DishdetailComponent'
import About from './AboutComponent';
import Home from './HomeComponent';
import Contact from './ContactComponent';
import { Switch, Route, Redirect, withRouter } from 'react-router-dom'
import { connect } from 'react-redux';
const mapStateToProps = state => {
return {
dishes: state.dishes,
comments: state.comments,
promotions: state.promotions,
leaders: state.leaders
}
}
class Main extends Component {
render() {
const HomePage = () => {
return (
<Home
dish={this.props.dishes.filter((dish) => dish.featured)[0]}
promotion={this.props.promotions.filter((promo) => promo.featured)[0]}
leader={this.props.leaders.filter((leader) => leader.featured)[0]}
/>
);
}
const DishWithId = ({ match }) => {
return (
<DishDetail dish={this.props.dishes.filter((dish) => dish.id === parseInt(match.params.dishId, 10))[0]}
comments={this.props.comments.filter((comment) => comment.dishId === parseInt(match.params.dishId, 10))} />
);
};
return (
<div>
<Header />
<div>
<Switch>
<Route path='/home' component={HomePage} />
<Route exact path='/aboutus' component={() => <About leaders={this.props.leaders} />} />} />
<Route exact path='/menu' component={() => <Menu dishes={this.props.dishes} />} />
<Route path='/menu/:dishId' component={DishWithId} />
<Route exact path='/contactus' component={Contact} />} />
<Redirect to="/home" />
</Switch>
</div>
<Footer />
</div>
);
}
}
export default withRouter(connect(mapStateToProps)(Main));
The package that you are using for handling forms- 'react-redux-form' is currently facing issues with React 6. It is currently in maintenance mode.
You can either use alternatives like Formik or downgrade to a stable version of 'React' and 'react-native-form'.
Hope this helps :)
When calling history.push('/packages') the url is updated but the component will not mount (render) unless the page is reloaded. If I call createHistory({forceRefresh: true}) or manually reload the page the UI is rendered correctly. How can I configure react-router-dom to load the component without explicitly reloading the page or using forceRefresh?
index.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import store from './store'
import {Provider} from 'react-redux'
import App from './App'
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('app')
);
App.jsx
import React, { Component } from 'react'
import { Switch, Route } from 'react-router-dom'
import PropTypes from 'prop-types'
import { Navbar, PageHeader, Grid, Row, Col } from 'react-bootstrap'
import LoginFormContainer from './components/Login/LoginFormContainer'
import PackageContainer from './components/Package/PackageContainer'
class App extends Component {
render() {
return (
<Grid>
<Navbar>
<Navbar.Header>
<Navbar.Brand><h3>Mythos</h3></Navbar.Brand>
</Navbar.Header>
</Navbar>
<Row className="content">
<Col xs={12} md={12}>
<Switch>
<Route path='/packages' render={() => <PackageContainer />} />
<Route exact path='/' render={() => <LoginFormContainer />} />
</Switch>
</Col>
</Row>
</Grid>
)
}
}
export default App
loginActions.jsx
import * as types from './actionTypes'
import LoginApi from '../api/loginApi'
import createHistory from 'history/createBrowserHistory'
const history = createHistory()
export function loginUser(user) {
return function(dispatch) {
return LoginApi.login(user).then(creds => {
dispatch(loginUserSuccess(creds));
}).catch(error => {
throw(error);
});
}
}
export function loginUserSuccess(creds) {
sessionStorage.setItem('credentials', JSON.stringify(creds.data))
history.push('/packages')
return {
type: types.LOGIN_USER_SUCCESS,
state: creds.data
}
}
PackageContainer.jsx
import React, { Component } from 'react'
import {connect} from 'react-redux'
import PropTypes from 'prop-types'
import {loadPackages} from '../../actions/packageActions'
import PackageList from './PackageList'
import ImmutablePropTypes from 'react-immutable-proptypes'
import {Map,fromJS,List} from 'immutable'
import {withRouter} from 'react-router-dom'
class PackageContainer extends Component {
constructor(props, context) {
super(props, context);
}
componentDidMount() {
this.props.dispatch(loadPackages());
}
render() {
return (
<div className="col-lg-12">
{this.props.results ?
<PackageList results={this.props.results} /> :
<h3>No Packages Available</h3>}
</div>
);
}
}
PackageContainer.propTypes = {
results: ImmutablePropTypes.list.isRequired,
};
const mapStateToProps = (state, ownProps) => {
return {
results: !state.getIn(['packages','packages','results']) ? List() : state.getIn(['packages','packages','results'])
};
}
PackageContainer = withRouter(connect(mapStateToProps)(PackageContainer))
export default PackageContainer
I assume, that issue that you are create new instance of history object but BrowserRouter doesn't know about the changes which happens inside of it.
So, you should create history object and export it in the index.jsx and use Router instead of BrowserRouter and pass as history property, so then you can just import it whenever you need.
For example:
index.jsx
import { Router } from 'react-router-dom'
import createHistory from 'history/createBrowserHistory'
...
export const history = createHistory()
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<App />
</Router>
</Provider>,
document.getElementById('app')
);
Then, in loginActions you just import history and use .push method as before.
loginActions.jsx
import * as types from './actionTypes'
import LoginApi from '../api/loginApi'
import { history } from './index'
export function loginUser(user) {
return function(dispatch) {
return LoginApi.login(user).then(creds => {
dispatch(loginUserSuccess(creds));
}).catch(error => {
throw(error);
});
}
}
export function loginUserSuccess(creds) {
sessionStorage.setItem('credentials', JSON.stringify(creds.data))
history.push('/packages')
return {
type: types.LOGIN_USER_SUCCESS,
state: creds.data
}
}
Hope it will helps.
In the App.jsx
<Switch>
<Route path='/packages' render={(props) => <PackageContainer {...props}/>} />
<Route exact path='/' render={(props) => <LoginFormContainer {...props}/>} />
</Switch>
Now both PackageContainer and LoginFormContainer have access to history object
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;