React Router + Redux: history is undefined - reactjs

I have the following component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { logoutUser } from '../actions';
class NavBar extends Component {
onLogoutClick() {
this.props.logoutUser();
this.props.history.push('/login'); // here is the issue
}
render() {
return (
<div>
<button
className="btn btn-danger pull-xs-right"
onClick={this.onLogoutClick.bind(this)}
>
Logout
</button>
</div>
);
}
}
export default connect(null, {logoutUser})(NavBar);
I get this error in the console:
Cannot read property 'push' of undefined
Why doesn't this component have history in its props ?
Update 1
This is my App.js which hosts BrowserRouter:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import promise from 'redux-promise';
import reducers from './reducers';
import LoginForm from './components/login_form';
import NavBar from './components/nav_bar';
import Homepage from './components/home_page';
import PrivateRoute from './components/private_route';
const createStoreWithMiddleware = applyMiddleware(promise)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<BrowserRouter>
<div>
<PrivateRoute exact path="/" component={Homepage} />
<Route exact path="/login" component={LoginForm} />
</div>
</BrowserRouter>
</Provider>
, document.querySelector('.container'));
and this is the PrivateRoute:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import Homepage from './home_page';
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
<Component {...props} />
props.location } }} />
)} />
)
export default PrivateRoute;
and Homepage.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import NavBar from './nav_bar';
export default class HomePage extends Component {
render() {
return (
<div>
<NavBar />
<h1>Welcome to the homepage</h1>
</div>
);
}
}
Update 2
I also have LoginForm component which is more or less the same as the previous component, but it works fine:
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { loginUser } from '../actions';
import validator from 'validator';
class LoginForm extends Component {
renderField(field) {
//....
}
onSubmit(values) {
this.props.loginUser(values, () => {
this.props.history.push('/');
});
}
render() {
const {handleSubmit} = this.props;
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
name="username"
label="Username"
component={this.renderField}
/>
<Field
name="password"
label="Password"
component={this.renderField}
/>
<button type="submit" className="btn btn-primary">Enter</button>
</form>
);
}
}
function validate(values) {
const errors = {};
return errors;
}
export default reduxForm({
validate,
form:'LoginForm'
})(
connect(null, { loginUser })(LoginForm)
);

You need to decorate your component with withRouter
eg. add to the top:
import { withRouter } from 'react-router';
and change your export to:
export default withRouter(connect(null, {logoutUser})(NavBar));
This will give you the react router props.
See https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md

Related

React Router route not rendering component

I'm trying to redirect my HomePage Component to a different component with one click of a button. However, when the link is clicked it's changing the path in the browser URL but I'm not able to see any change (i.e., my new Addbook component is not being rendered inside Route).
App.js
import React, { Component, useState } from "react";
import axios from "axios";
import "./styles.css";
import HomePage from "./HomePage.js";
import Addbook from "./AddBook.js";
import {Router,Switch,Route, BrowserRouter} from "react-router-dom";
class App extends React.Component {
render()
{
return (
<BrowserRouter>
<Switch>
<Route exact path="/"><HomePage></HomePage></Route>
<Route path="/add"><Addbook></Addbook></Route>
</Switch>
</BrowserRouter>
);
}
}
export default App;
HomePage.js
import React from "react";
import {BrowserRouter, Link, Router, Switch, useHistory } from "react-router-dom";
class HomePage extends React.Component {
render() {
return ( <div>
<h1>Book Library</h1>
<BrowserRouter>
<Switch>
<Link to="/add"><button id="addbook">Add Book</button></Link>
<Link to="/search"><button id="searchbook">Search Book</button></Link>
</Switch>
</BrowserRouter>
</div>
)
}
}
export default HomePage
AddBook.js
import React from "react";
import axios from "axios";
const addBook = () => {
axios.post("http://localhost:3000/addbook",{
name: this.state.name,
author : this.state.author,
date: this.state.date,
category: this.state.category
}).then(response => response.data).then(data => {
console.log(data);
if(data=="added")
{
}
}, (error) => {
console.log(error);
});
}
class Addbook extends React.Component{
render(){
return (<div>
New Book<input type="text" onChange={(e)=>{
this.setState({name: e.target.value})
}} name="name" id="newbook"/>
Author<input type="text" onChange={(e)=>{
this.setState({author: e.target.value})
}} name="author" id="Author"/>
Date of Publish<input type="date" onChange={(e)=>{
this.setState({date: e.target.value})
}} name="date" id="dtofpub"/>
Category<input type="text" onChange={(e)=>{
this.setState({category: e.target.value})
}} name="category" id="category"/>
<button id="add" onClick={addBook}>ADD</button>
</div> )
}
}
export default Addbook;
you should update homePage like this :
import React from "react";
import {BrowserRouter, Link, Router, Switch, useHistory } from "react-router-dom";
class HomePage extends React.Component {
render() {
return (
<div>
<h1>Book Library</h1>
<Link to="/add"><button id="addbook">Add Book</button></Link>
<Link to="/search"><button id="searchbook">Search Book</button></Link>
</div>
)
}
}
export default HomePage
There is syntax error please replace
<Route exact path="/"><HomePage></HomePage></Route>
To
<Route exact path="/" component={HomePage}></Route>

How to get ReactRouter Props , while using Redux

App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
// import browserRoute
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom'
// redux
import Todo from './todo/Todo';
import Login from './todo/Login';
import Home from './todo/Home';
import Callender from './Callender/Callender'
import WhetherData from './Callender/CallenderComponent'
import { localstoreExport } from './index';
export default class App extends Component {
render() {
return (
<BrowserRouter>
< Switch>
<Route path="/todo">
<Todo />
</Route>
<Route path="/login">
<Login />
</Route>
<Route path ="/callender">
<Callender />
</Route>
<Route path="/climateshow" component ={WhetherData}/>
<Route exact path="/" component={Home}/>
</Switch>
</BrowserRouter>
</Provider>
)
}
}
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
// Redux
import { createStore,combineReducers,applyMiddleware,compose } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
// import reducer
import todoReducer from './store/reducers/rootReducer';
import whetherReducer from './store/reducers/callender'
// combine the Reducer
const rootReducer = combineReducers({
todoReducer,
whetherReducer
})
// chrome extension helper
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
// create a store
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
export const localstoreExport = store;
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));
Callender.js
import React, { Component } from 'react';
import { DatePicker, DatePickerInput } from 'rc-datepicker';
import 'rc-datepicker/lib/style.css';
import axios from 'axios'
import { connect } from 'react-redux';
// redux components
import { getDataFromApi } from '../store/actions/callender'
class Callender extends Component {
state = {
totalDate:''
}
// These is the complete code for getting the response from version 2 of api
onChange = (event) =>{
console.log(event);
this.setState({
totalDate:event
})
this.props.getWhetherData()
{console.log(this.props.whetherData)}
}
render() {
const date = '2015-06-26';
return (
<div>
<DatePicker onChange={this.onChange} value={date} />
{this.props.whetherData? console.log(this.props.history): null}
</div>
)
}
}
const mapStateToProps = state =>{
return {
whetherData: state.whetherReducer.whetherData
}
}
const mapDispatchToProps = dispatch =>{
return {
getWhetherData : () => dispatch(getDataFromApi())
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Callender);
1)In the following code I have written my Routes, in App.js file and created store in Index.js
2) In Callender.js i want to push my route to whether data end Point so i am trying to see the this.props.history it is showing undefined
3) so i tried to see my props by this.props Now it is showing me the redux props so i realized that
my router props are getting overridden by redux props
4) how to use history push while using redux
You can wrap your component with withRouter.
import { withRouter } from "react-router";
export default connect(mapStateToProps,mapDispatchToProps)(withRouter(Callender));
You can find detailed example here.

React-Redux 6 error: Passing redux store in props has been removed and does not do anything

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 :)

react-router-dom route component requires page reload

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

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