React Router Redux upgrade from v3 to v4 - javascript

I am trying to upgrade react-router from v3 to v4 and getting the following error when running the application :
Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in.
reducer :
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import { login } from './login-reducer';
const reducers = combineReducers({
routing: routerReducer,
login
})
export default reducers;
store :
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk';
import createHistory from 'history/createBrowserHistory'
import { routerMiddleware } from 'react-router-redux'
import reducers from '../reducers';
export const history = createHistory();
const routerMiddle = routerMiddleware(history);
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
}) : compose;
let middleware = [routerMiddle, thunk]
const enhancer = composeEnhancers(
applyMiddleware(...middleware),
// other store enhancers if any
);
const store = createStore(reducers, enhancer);
export default store;
index :
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import store, { history } from './store/createStore';
import { ConnectedRouter } from 'react-router-redux'
import { getRoutes } from './routes';
import './index.css';
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<div>{ getRoutes(store) }</div>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
);
routes :
import React from 'react';
import { Route } from 'react-router';
import App from './containers/App';
import Login from './containers/Login';
import Protected from './components/Protected';
export const getRoutes = (store) => {
const authRequired = (nextState, replaceState) => {
// Now you can access the store object here.
const state = store.getState();
if (!state.login.loggedIn || state.login.loggedIn == null) {
// Not authenticated, redirect to login.
replaceState({
pathname: '/login',
state: { nextPathname: nextState.location.pathname }
});
}
};
return (
<div>
<Route exact path="/" component={App} />
<Route path="/login" component={Login} />
<Route path="/protected" component={Protected} onEnter={authRequired} />
</div>
);
}
app :
import React, { Component } from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux'
import logo from '../logo.svg';
import './App.css';
class App extends Component {
render() {
const isLoggedIn = this.props.isLoggedIn;
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2><Link to="/">Welcome to React</Link></h2>
<div className="app-nav">
<nav>
<Link to='about'>About</Link>
<Link to='login'>{( isLoggedIn ? 'Logout' : 'Login' )}</Link>
<Link to='protected'>Protected</Link>
</nav>
</div>
</div>
{this.props.children}
</div>
);
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: (state.login.loggedIn ? state.login.loggedIn : false)
}
}
App = connect (
mapStateToProps
)(App)
export default App;
Not sure what I'm missing to get this to work
**NOTE : I am still seeing this error even when I only have one route / and render a static component (single div with text)

A) You dont need react router redux at all with react router 4, specially if all you want is to render authenticated components.
B) The onEnter hook dont work this way in RR4, its the older way of doing route validation
{this.props.children} why are you still rendering child routes using props.children? All the child routes goes into the component it resides in.
If you are trying to learn rr4, i recommend checking this boilerplate for it https://github.com/touqeerkhan11/The-Ultimate-Boilerplate

The problem is probably here
import { Link } from 'react-router';
react-router doesn't export Link, react-router-dom does.
import { Link } from 'react-router-dom';
You should probably import the Route component from react-router-dom as well.

The issue had to do with the version of react-router-redux that was being used. When I added it to the project, I left off the #next

Related

React Admin | Uncaught Error: Missing history prop

I am trying to implement react-admin in my project however upon render I get this message
Uncaught Error: Missing history prop. When integrating react-admin inside an existing redux Provider, you must provide the same 'history' prop to the <Admin> as the one used to bootstrap your routerMiddleware. React-admin uses this history for its own ConnectedRouter.
There is very little to be found about this issue and I'm not entirely sure how to go about setting the history. I've tried importing createHistory from 'history/createHashHistory' but then I get this error
Uncaught Could not find router reducer in state tree, it must be mounted under "router"
Is there a way to get this rendering properly? And if so, what would be the proper way to go about configuring the Admin Component's history?
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { createStore, compose, applyMiddleware} from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { BrowserRouter as Router } from 'react-router-dom';
import { reducer } from './redux/reducer';
import * as serviceWorker from './serviceWorker';
const store = createStore(reducer, compose(
applyMiddleware(thunk),
window.navigator.userAgent.includes('Chrome') ?
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() : compose,
),
);
ReactDOM.render(
<React.StrictMode>
<Router>
<Provider store={store}>
<App />
</Provider>
</Router>
</React.StrictMode>,
document.getElementById('root')
);
AdminPage.js
import { Admin, Resource } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';
const dataProvider = jsonServerProvider('http://localhost:3000');
const AdminPage = () => {
return (
<Admin dataProvider={dataProvider}>
<Resource name="services" />
</Admin>
)
}
export default AdminPage;
App.js
import React from 'react';
import './App.css';
import AdminPage from './components/AdminPage';
import { Switch, Route } from 'react-router-dom';
const App = () => {
return (
<div className="app">
<Switch>
<Route exact path='/manage' component={ AdminPage } />
</Switch>
</div>
);
}
export default App;
The proper way to write a reducer in this case is documented at https://marmelab.com/react-admin/CustomApp.html#using-an-existing-redux-provider.
But from what I see, you don't need one. You just added the thunk middleware, which is useless in react-admin as it already handles sagas.

React Redux prop is undefined

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.

Prop not available in componentDidMount

How do I use redux to fire a actionCreator to get my initial data.
I need a place to get my initial data when the app loads.
I put it here but actionNoteGetLatest is not a prop yet.
Please can you help.
componentDidMount() {
// This is where the API would go to get the first data.
// Get the notedata.
this.props.actionNoteGetLatest();
}
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
// Redux
import { Provider, connect } from 'react-redux';
// TODO: Add middle ware
// import { createStore, combineReducers, applyMiddleware } from 'redux';
import { createStore } from 'redux';
import { PropTypes } from 'prop-types';
// Componenets
import PageHome from './components/pages/PageHome';
import PageOther from './components/pages/PageOther';
import registerServiceWorker from './registerServiceWorker';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
import '../node_modules/font-awesome/css/font-awesome.min.css';
import './styles/index.css';
import rootReducer from './Reducers/index';
import { actionNoteGetLatest } from './actions/noteActions';
// TODO: Turn redux devtools off for production
// const store = createStore(combineReducers({ noteReducer }), {}, applyMiddleware(createLogger()));
/* eslint-disable no-underscore-dangle */
const store = createStore(
rootReducer,
{},
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
);
/* eslint-enable */
class Main extends Component {
// constructor(props) {
// super(props);
// this.state = {
// };
// }
componentDidMount() {
// This is where the API would go to get the first data.
// Get the notedata.
this.props.actionNoteGetLatest();
console.log(this);
}
render() {
return (
<Provider store={store}>
<div className="Main">
<Router>
<Switch>
<Route exact path="/" component={PageHome} />
<Route path="/other" component={PageOther} />
</Switch>
</Router>
</div>
</Provider>
);
}
}
connect(null, { actionNoteGetLatest })(Main);
Main.propTypes = {
actionNoteGetLatest: PropTypes.func.isRequired,
};
ReactDOM.render(<Main />, document.getElementById('root'));
registerServiceWorker();
noteActions.js
import actionTypes from '../constants/actionTypes';
export const actionNoteGetLatest = () => ({
type: actionTypes.NOTE_GET_LATEST,
});
The problem is that you are rendering the initial Main component instead of the connected one. Update the line with the connect call to:
const MainWrapper = connect(null, { actionNoteGetLatest })(Main);
Then, use the MainWrapper component in your render:
ReactDOM.render(<MainWrapper />, document.getElementById('root'));
Check that currently you are rendering the <Main/> component without providing any prop.

Adding hashrouter to routes makes 'push' to stop render components

I have a ConnectedRouter to which I wanted to add hashes to all the routes so I added the HashRouter component like this:
// #flow
import React from 'react';
import { Router, Route,Switch } from 'react-router'
import { HashRouter } from 'react-router-dom'
import { ConnectedRouter } from 'react-router-redux';
import { routerActions } from 'react-router-redux';
import { UserAuthWrapper } from 'redux-auth-wrapper';
import StudiesViewContainer from './components/views/studies/StudiesViewContainer';
import NotificationsViewContainer from './components/views/notifications/NotificationsViewContainer';
import UserView from './components/views/user/UserView';
import StudyViewContainer from './components/views/studies/StudyViewContainer';
import { getUser } from './reducers';
import LoginView from './components/views/login';
import NotFoundView from './components/views/notFound';
import ForbiddenView from './components/views/forbidden';
const UserIsAuthenticated = UserAuthWrapper({
authSelector: getUser,
redirectAction: routerActions.replace,
failureRedirectPath: '/',
wrapperDisplayName: 'UserIsAuthenticated'
});
const configRouter = (history: Object) => {
return () =>
<ConnectedRouter history={ history }>
<HashRouter>
<Switch>
<Route path="/studies" component={ StudiesViewContainer } />
<Route path="/study/:id" component={ StudyViewContainer } />
<Route path="/user" component={ UserView } />
<Route path="/notifications" component={ NotificationsViewContainer } />
<Route path="/forbidden" component={ ForbiddenView } />
<Route path="/not-found" component={ NotFoundView } />
<Route path="/" component={ LoginView } />
<Route path="*" component={ NotFoundView } />
</Switch>
</HashRouter>
</ConnectedRouter>
};
export default configRouter;
The problem is that when I do something like this:
push('studies')
The route does not add the hash and the new components are not rendered.
I added the browser history to my store, here is the configureStore file:
// #flow
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import createHistory from 'history/createBrowserHistory'
import { routerMiddleware } from 'react-router-redux';
import createSagaMiddleware from 'redux-saga'
import {
persistStore,
autoRehydrate,
createTransform
} from 'redux-persist';
import mainSaga from '../sagas';
import reducer from '../reducers';
const history = createHistory();
const routingMiddleware = routerMiddleware(history);
const sagaMiddleware = createSagaMiddleware();
// Remove error field from persisted auth substate
let authTransform = createTransform(
(inboundState, key) =>
key === 'auth' ?
{ ...inboundState, error: undefined }:
inboundState,
outboundState => outboundState,
{
whitelist: [
'auth',
'permissions'
]
}
);
const configureStore = (): Promise<*> => {
let middlewares = [routingMiddleware,thunk, sagaMiddleware ];
let composeEnhancers = compose;
if(process.env.NODE_ENV !== 'production') {
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
}
const store = createStore(
reducer,
composeEnhancers(
applyMiddleware(...middlewares),
autoRehydrate()));
sagaMiddleware.run(mainSaga);
return new Promise(resolve => {
persistStore(
store, {
whitelist: ['auth', 'permissions'],
debounce: 500,
transforms: [
authTransform
]
},
() => resolve({ store, history })
);
});
};
export default configureStore;
Can anyone help me get the push working as expected?
I am using the following versions of router in package json:
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"react-router-redux": "next",
I had a similar problem, I solved it by using
import createHistory from 'history/createHashHistory'
instead of
import createHistory from 'history/createBrowserHistory'
Also added withRouter when exporting components as suggested in
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/blocked-updates.md
I added the browser history to my store, here is the configureStore file:
Most likely that issue lies on difference between history-object, used in router, and proposed in redux state. Then history created in redux side const history = createHistory();, it seems that it had not linked with ConnectedRouter object, but it can't be followed by supposed code.
If valid history object is passed to ConnectedRouter, try to check that subsequent router actions are to intercepted with saga process or other middleware. Try to follow on emitting actions flow by https://github.com/gaearon/redux-devtools tool.
Also you can try to perform manual updating, by adding root component with full lifecycle (Inherited from React.Component class), and add there following code:
this.props.history.listen((location, action) => {
console.log("on route change");
});
Then you can discover had router state been changed, and if action is taken, just perform forceUpdate method in root component. Also consult this documentation: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/blocked-updates.md

Nothing happening and no errors with dispatch functions

I'm managing my React application route with react-router and react-router-redux.
I setting router with following configuration:
import {Provider} from 'react-redux'
import {Router, browserHistory} from 'react-router'
import {createStore, combineReducers} from 'redux'
import {routerReducer} from 'react-router-redux';
import {syncHistoryWithStore} from 'react-router-redux'
import Reducers from './reducers'; // contains all my reducers,
const store = createStore(combineReducers(
{...Reducers, routerReducer}
));
const history = syncHistoryWithStore(browserHistory, store);
const AppRoute = (
<Provider store={store}>
<Router history={history}>
<Route path='login' components={PageLogin}/>
<Route path='system' components={PageSystem}>
<Route path="profile" component={PageProfile}/>
</Route>
</Router>
</Provider>
);
export default AppRoute;
When I try dispatch any router function:
import React from 'react';
import {Link, Route} from 'react-router';
import {connect} from 'react-redux';
import {replace, push} from 'react-router-redux';
class SystemMenu extends React.Component {
constructor(props) {
super(props);
this.logout = this.logout.bind(this);
}
logout() {
this.props.logout();
}
render() {
return (
<ul className="system-menu">
<li className={this.isActual("/system/profile")}>
<Link to="/system/profile">Profile</Link>
</li>
<li className={this.isActual("/system/unit")}>
<Link to="/system/unit">Units</Link>
</li>
<li>
<button onClick={this.logout}>Logout</button>
</li>
</ul>
)
}
}
const mapStateToProps = (state) => {
return {
router: state.router,
actualPage: state.menu.actualPage
};
};
const mapDispatchToProps = (dispatch) => {
return {
logout() {
dispatch(push('/login'));
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(SystemMenu);
When I click que Link component, pages is changing, But when I click the Logout button to debug, is executing dispatch(push('/login')); line, but nothing has happening, and no occur any error in the console.
What I worse in the configuration?
I tried console.log the logout inside the mapDispatchToProps function, and returning logout is not defined
const mapDispatchToProps = (dispatch) => {
console.log(push, logout);
return {
logout() {
dispatch(push('/login'));
}
};
};
combineReducers should take an object as the first argument, like this:
const store = createStore(
combineReducers({
...reducers,
routing: routerReducer
})
)
You're giving it an Array instead.
I suspect the reason why push doesn't work is because the router reducer is not registered properly.
I resolved this problem.
This problem is occurring because I has not using react-routerredux` middleware, so when I dispatching, maybe is not updating browser URL by state.
Following my route configuration:
import {Provider} from 'react-redux'
import {Router, browserHistory} from 'react-router'
import {createStore, combineReducers, applyMiddleware} from 'redux'
import {routerReducer} from 'react-router-redux';
import {syncHistoryWithStore, routerMiddleware} from 'react-router-redux'
import Reducers from './reducers'; // contains all my reducers,
const middleware = [routerMiddleware(browserHistory)];
const store = createStore(combineReducers(Reducers), compose(
applyMiddleware(...middleware)
));
const history = syncHistoryWithStore(browserHistory, store);
const AppRoute = (
<Provider store={store}>
<Router history={history}>
<Route path='login' components={PageLogin}/>
<Route path='system' components={PageSystem}>
<Route path="profile" component={PageProfile}/>
</Route>
</Router>
</Provider>
);
export default AppRoute;

Categories

Resources