I'm trying to use the context router in a component and I can't understand why it is undefined.
After searching for similar questions I've tried different solutions, such as:
wrapped the withRouter hoc into my component
added the contextTypes with router
made sure that the App component is wrapped with BrowserRouter
tried to access the router with both this.props.router and this.context.router
This is my code:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import { AppRedux } from 'components';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<Router>
<AppRedux />
</Router>
</Provider>,
document.getElementById('app')
);
AppRedux.js
import { connect } from 'react-redux';
import AppContainer from './AppContainer';
const mapDispatchToProps = (dispatch) => ({
// ...
})
const mapStateToProps = (state) => ({
// ...
})
export default connect(mapStateToProps, mapDispatchToProps)(AppContainer);
AppContainer.js - here is where I'm trying to access the context router
import React from 'react';
import { withRouter } from 'react-router'
class AppContainer extends React.Component {
constructor() {
super();
this.state = {};
}
render() {
console.log(this.context) // { router: undefined }
console.log(this.props.router) // { router: undefined }
return (
// ...
);
}
}
AppContainer.contextTypes = {
router: PropTypes.object.isRequired
}
export default withRouter(AppContainer);
Related
Beginner question.
I want to pass a user object to a component from store as a prop, but the component doesn't get it (undefined). I get the user object from a third party service authentication service (google firebase)
The middleware actually logs out in the console that the action of type SET_CURRENT_USER takes place, and next state indeed will have a user.currentUser set to the object returned from the login service (NOT UNDEFINED).
However, the component doesn't re-render and doesn't seem to receive the object as prop
The component, in which the prop is undefined
import React from 'react';
import { connect } from 'react-redux';
import { auth } from "../../firebase/firebase.utils";
export const Navbar = ({ currentUser }) => {
return (
/* A LOT OF JSX CODE. currentUser IS UNDEFINED */
);
};
const mapStateToProps = state => ({
currentUser: state.user.currentUser
});
export default connect(mapStateToProps)(Navbar);
The App component, which has the above component as a child. Also, I'm trying to set the store to contain the user object in the componentDidMount()
import React from 'react';
import Homepage from "./pages/homepage";
import { Route, Switch } from 'react-router-dom';
import { connect } from 'react-redux';
import Login from "./pages/login";
import Register from "./pages/register";
import { Navbar } from "./components/navbar/navbar";
import { auth } from "./firebase/firebase.utils";
import { setCurrentUser } from "./redux/user/user.actions";
class App extends React.Component {
unsubscribeFromAuth = null;
componentDidMount() {
this.unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
if(userAuth) {
(async () => {
const rawResponse = await fetch(/* JUST AN ASYNC FUNCTION TO POST TO BACKEND*/);
})();
}
this.props.setCurrentUser(userAuth); /*HERE IM TRYING TO SET THE STORE*/
})
}
componentWillUnmount() {
this.unsubscribeFromAuth();
}
render() {
return (
<div>
<Navbar /> /* THE COMPONENT WHICH SHOULD GET THE USER OBJECT AS PROP */
<Switch>
<Route exact={true} path={'/register'} component={Register} />
<Route exact={true} path={'/login'} component={Login} />
<Route path={'/'} component={Homepage} />
</Switch>
</div>
);
}
}
const mapDispatchToProps = (dispatch) => ({
setCurrentUser: user => dispatch(setCurrentUser(user))
});
export default connect(null, mapDispatchToProps)(App);
The index component
import React from 'react';
import ReactDOM from 'react-dom';
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import { Provider } from 'react-redux';
import store from "./redux/store";
ReactDOM.render(
<Provider store={store} > /* HERE IS STORE PROVIDED FROM IMPORT*/
<BrowserRouter>
<App/>
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
Root reducer
import { combineReducers } from "redux";
export default combineReducers({
user: userReducer
});
User reducer
const INITIAL_STATE = {
currentUser: null
};
const userReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'SET_CURRENT_USER':
return {
...state,
currentUser: action.payload
};
default:
return state;
}
};
export default userReducer;
User action
export const setCurrentUser = user => ({
type: 'SET_CURRENT_USER',
payload: user
});
The store
import { createStore, applyMiddleware } from "redux";
import logger from 'redux-logger';
import rootReducer from './root-reducer';
const middlewares = [logger];
const store = createStore(rootReducer, applyMiddleware(...middlewares));
export default store;
You're doing both a named and default export for Navbar. The default export gets wrapped by the connect HOC that adds currentUser to its props. The named export does not.
You import it named like this: import { Navbar } from. Instead use the default export: import Navbar from.
Then I would suggest removing the named export to avoid future confusion.
I am building an app prototype that essentially simulates ecommerce. I have components that each have different items that can be added to a cart(below I just show an example of how one would basically work). These components are accessed via different routes using the react-router. There is a header component that displays the number of items currently in the cart. The header gets the number of items in the cart from the state in the redux store. However, if I navigate to a new route, the store goes back to the default state. I need the the store to keep its state when a new route is navigated to. For example, if I go to the ShoppingPage, add an item to the cart, and then go back to the Home page, I need the cart to still have an item in it.
actions.js
export const actionTypes = Object.freeze({
UPDATE_CART: Symbol('UPDATE_CART'),
});
export const updateCart = (payload) => {
return {
type: actionTypes.UPDATE_CART,
payload,
};
};
export default actionTypes;
reducer.js
import actions from './actions';
export const INITIAL_STATE = {
cart: [],
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case actions.UPDATE_CART: {
return {
...state,
cart: action.payload,
};
}
default: {
return state;
}
};
};
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducer, { INITIAL_STATE } from './reducer';
const store = createStore(reducer, INITIAL_STATE);
console.log(store.getState());
ReactDOM.render(
<Provider store ={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
, document.getElementById('root'));
serviceWorker.unregister();
ShoppingPage.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { updateCart } from './actions';
class ShoppingPage extends Component {
addToCart = () => {
const cart = [...this.props.cart];
cart.push('new item');
this.props.modifyCart(cart);
render() {
return(
<div>
<button onClick={addToCart}>
Add To Cart
</button>
</div>
)
}
}
const mapDispatchToProps = dispatch => ({
modifyCart: payload => dispatch(updateCart(payload)),
});
const mapStateToProps = state => ({
cart: state.cart,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ShoppingPage);
Home.js
import React, { Component } from 'react';
import { ListGroup, ListGroupItem } from 'reactstrap';
class Home extends Component {
render() {
return(
<div>
<ListGroup>
<ListGroupItem><a href='/ShoppingPage'>ShoppingPage</a></ListGroupItem>
</div>
)
}
}
export default Home;
Header.js
import React, { Component } from 'react';
import { Navbar, NavbarBrand } from 'reactstrap';
import { connect } from 'react-redux';
class Header extends Component {
render() {
return(
<Navbar sticky='top' className='nav'>
<NavbarBrand href='/'>Online Shopping</NavbarBrand>
<span>{'Items in Cart: '}{this.props.cart.length}</span>
</Navbar>
)
}
}
const mapStateToProps = state => ({
cart: state.cart,
});
export default connect(
mapStateToProps
)(Header);
Routes.js
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Home from './Home';
import ShoppingPage from './ShoppingPage';
const Routes = () => (
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/ShoppingPage' component={ShoppingPage} />
</Switch>
);
export default Routes;
App.js
import React from 'react';
import Routes from './Routes';
import Header from './Header';
function App() {
return (
<div>
<Header />
<Routes />
</div>
);
}
export default App;
What's likely happening is that during navigation the web app "reloads" again (which is wiping the redux state). In order to navigate with react router you want to look at <Link>.
For example,
Home.js
<a href='/ShoppingPage'>ShoppingPage</a>
should be changed to:
<Link to="/ShoppingPage">ShoppingPage</Link>
I'm getting below error even i had defined store for root component.
click to enlarge image
Not sure why im getting this error even after defining store.
index.js (rootpage)
import React, {Component} from 'react';
import {initStore} from './redux/store';
import {Provider} from 'react-redux';
import App from './App.container';
const store = initStore();
class BuddApp extends Component {
render () {
return (
<Provider store={store}>
<App />
</Provider>
);
}
}
export default BuddApp;
This is app.container.js which is inside app.
app/app.container.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
import Router from './routes';
import Proptypes from 'prop-types';
import {addNavigationHelpers} from 'react-navigation';
class App extends Component {
render () {
const {dispatch, nav, userPreferences} = this.props;
console.log(this.props)
return (
<Router screenProps={userPreferences} navigation={addNavigationHelpers({dispatch, state: nav})}/>
</Provider>
);
}
}
App.propTypes = {
dispatch: Proptypes.func,
nav: Proptypes.object,
userPreferences: Proptypes.object
};
const mapStateToProps = ({nav, userPreferences}) => ({
nav,
userPreferences
});
const mapDispatchToProps = (dispatch) => ({
dispatch
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
app/pages/login.page.js
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import Login from '../components/Login/Login.component';
import {NavigationActions} from 'react-navigation';
class LoginPage extends Component {
onLoginPress(){
console.log("fsdfds")
this.props.navigation.navigate('setupProfile')
}
render () {
const {state} = this.props;
return (
<Login onLoginPress={this.onLoginPress.bind(this)} />
);
}
}
LoginPage.propTypes = {
onLoginPress: PropTypes.func
};
const mapStateToProps = (state) => ({
state:state
});
const mapDispatchToProps = (dispatch) => ({
saveLoginDetails: dispatch(addProfile(f))
});
export default connect(mapStateToProps,mapDispatchToProps)(LoginPage);
This results in getting the above mentioned error. as you can see I am passing the store in the same manner as shown in the redux example.
Am i defined store in wrong file?
Try this way:
import { createStore, applyMiddleware } from "redux"
const store = createStore(
<<Your combined reducers comes here>>,
applyMiddleware(<<All your middleware comes here>>))
Not sure why I'm getting this error, it happened when I added connect from redux to my Login component, so I could connect my store.
FAIL src/components/auth/Login.test.js
● Test suite failed to run
Invariant Violation: Could not find "store" in either the context or props of "Connect(LoginContainer)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(LoginContainer)".
Index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from "react-redux"
import { createCommonStore } from "./store";
import App from './App'
import css from './manage2.scss'
const store = createCommonStore();
const element = document.getElementById('manage2');
console.log("Index.js Default store", store.getState());
ReactDOM.render(
<Provider store={store}> // <-- store added here
<App />
</Provider>, element);
store.js
import React from "react"
import { applyMiddleware, combineReducers, compose, createStore} from "redux"
import thunk from "redux-thunk"
import { userReducer } from "./reducers/UserReducer"
import { authReducer } from "./reducers/AuthReducer"
export const createCommonStore = (trackStore=false) => {
const reducers = combineReducers({
user: userReducer,
user: authReducer
});
//noinspection JSUnresolvedVariable
const store = createStore(reducers,
compose(
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
if (trackStore) {
store.subscribe((() => {
console.log(" store changed", store.getState());
}));
}
return store;
};
App.js
import React from 'react'
import { BrowserRouter as Router } from 'react-router-dom'
import Routes from './components/Routes'
const supportsHistory = "pushState" in window.history
export default class App extends React.Component {
render() {
return (
<Router forceRefresh={!supportsHistory}>
<Routes />
</Router>
);
}
}
Routes.js
import React from 'react'
import { Route, Switch } from 'react-router-dom'
import LoginContainer from './auth/Login'
import Dashboard from './Dashboard'
import NoMatch from './NoMatch'
const Routes = () => {
return (
<Switch>
<Route exact={ true } path="/" component={ LoginContainer }/>
<Route path="/dashboard" component={ Dashboard }/>
<Route component={ NoMatch } />
</Switch>
);
}
export default Routes
Finally Login.js (code removed for brevity
import React from 'react'
import { connect } from "react-redux"
import { bindActionCreators } from 'redux';
import { setCurrentUser } from '../../actions/authActions'
import * as api from '../../services/api'
const mapDispatchToProps = (dispatch) => {
console.log('mapDispatchToProps', dispatch);
return {
setUser: (user) => {
bindActionCreators(setCurrentUser(user), dispatch)
}
}
}
class LoginContainer extends React.Component {
constructor(props) {
super(props)
this.state = {};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
}
handleSubmit(e) {
}
render() {
return (
<div className="app-bg">
...
</div>
)
}
}
export default connect(null, mapDispatchToProps)(LoginContainer);
Login.test
import React from 'react'
import ReactTestUtils from 'react-dom/test-utils'
import { mount, shallow } from 'enzyme'
import toJson from 'enzyme-to-json'
import { missingLogin } from '../../consts/errors'
import Login from './Login'
import Notification from '../common/Notification'
const loginComponent = shallow(<Login />);
const fakeEvent = { preventDefault: () => '' };
describe('<Login /> component', () => {
it('should render', () => {
const tree = toJson(loginComponent);
expect(tree).toMatchSnapshot();
});
it('should render the Notification component if state.error is true', () => {
loginComponent.setState({ error: true });
expect(loginComponent.find(Notification).length).toBe(1);
});
});
describe('User Login', () => {
it('should fail if no credentials are provided', () => {
expect(loginComponent.find('.form-login').length).toBe(1);
loginComponent.find('.form-login').simulate('submit', fakeEvent);
expect(loginComponent.find(Notification).length).toBe(1);
const notificationComponent = shallow(<Notification message={ missingLogin }/>);
expect(notificationComponent.text()).toEqual('Please fill out both username and password.');
});
it('input fields should be filled correctly', () => {
const credentials = { username: 'leongaban', password: 'testpass' };
expect(loginComponent.find('#input-auth-username').length).toBe(1);
const usernameInput = loginComponent.find('#input-auth-username');
usernameInput.value = credentials.username;
expect(usernameInput.value).toBe('leongaban');
const passwordInput = loginComponent.find('#input-auth-password');
passwordInput.value = credentials.password;
expect(passwordInput.value).toBe('testpass');
});
});
What do you see wrong here?
Redux recommends exporting the unconnected component for unit tests. See their docs.
In login.js:
// Named export for tests
export class LoginContainer extends React.Component {
}
// Default export
export default connect(null, mapDispatchToProps)(LoginContainer);
And in your test:
// Import the named export, which has not gone through the connect function
import { LoginContainer as Login } from './Login';
You can then specify any props that would have come from the store directly on the component.
You need to pass store as either a prop or context in your test. mount method accepts context as another parameter.
and how do you get store here? You create store the same way you created in app.js
You could use React's contextType or pass propType. You would need to declare it either as a prop or contextType.
Provider.contextTypes = {
Store: React.PropTypes.object.isRequired
};
Provider.propTypes= {
Store: React.PropTypes.object.isRequired
};
I am writing code with react and I just started using redux (because I require a container of sorts). However, I have been stuck at one place for a bit now.
I get this error -
Invariant Violation: Could not find "store" in either the context or
props of "Connect(HomePage)". Either wrap the root component in a
, or explicitly pass "store" as a prop to
"Connect(HomePage)".
I tried googling, and according to the troubleshooting section of react-redux, this can be checked using these three things:
1. Make sure you don’t have a duplicate instance of React on the page.
2. Make sure you didn’t forget to wrap your root component in < Provider>.
3. Make sure you’re running the latest versions of React and React Redux.
I have the following code that is the root (which is where the store is defined with the provider) -
import React from 'react';
import { Router, browserHistory } from 'react-router';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import reduxThunk from 'redux-thunk';
import routes from '../Routes';
import reducers from './reducers/reducers';
import actions from './actions/actions';
export default class AppRoutes extends React.Component {
render() {
const store = createStore(reducers, applyMiddleware(reduxThunk));
return (
<Provider store={store}>
<Router history={browserHistory} routes={routes} onUpdate={() => window.scrollTo(0, 0)}/>
</Provider>
);
}
}
And this error only happens on one of the two components I have -
// No error when connected only in this component
import React from 'react';
import { connect } from 'react-redux';
import * as actions from './actions/actions';
class Dashboard extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h1>Hello, {this.props.isAuthenticated.toString()}</h1>;
}
}
function mapStateToProps(state) {
return {
content: state.auth.content,
isAuthenticated: state.auth.authenticated
};
}
export default connect(mapStateToProps, actions)(Dashboard);
// Error thrown when I try to connect this component
import React from 'react';
import LoginPage from './LoginPage';
import Dashboard from './Dashboard';
import Loading from './Loading';
import { connect } from 'react-redux';
import * as actions from './actions/actions';
class HomePage extends React.Component {
constructor(props) {
super(props);
this.setState({
loading: true
});
}
render() {
var inPage = undefined;
if(this.props.isAuthenticated) {
console.log('Logged in');
inPage = <Dashboard user = {HomePage.user}/>;
}
else if (this.state.loading){
console.log('Loading');
inPage = <Loading />;
}
else {
console.log('Login');
inPage = <LoginPage />;
}
return (
<div>
{inPage}
</div>
);
}
}
function mapStateToProps(state) {
this.setState({
loading: false
});
return {
content: state.auth.content,
isAuthenticated: state.auth.authenticated
}
}
export default connect(mapStateToProps, actions)(HomePage);
Not sure if this is where your issue lies or not. So I may be way off base.
But, I would check to be sure you are passing down the children to your components. The way I've done this is in my App class as such:
import React, {PropTypes} from 'react';
import { connect } from 'react-redux';
class App extends React.Component{
render(){
return(
<div className="container-fluid">
{this.props.children}
</div>
);
}
}
App.propTypes={
children: PropTypes.object.isRequired
};
export default connect()(App);