React router throws and error when getting async component - react-router

I'm using react, redux and react router amoung others to build and example app.
I'm trying to load asynchronously different parts of my application. I've divided my app in ducks and I'm following this example https://github.com/insin/react-examples/tree/master/code-splitting-redux-reducers
But I'm getting this error:
Uncaught Invariant Violation: The root route must render a single element
When trying to get async component with getComponent method of react router.
I'm using:
react-router 2.0.1
My routes:
export default function configureRoutes(reducerRegistry) {
return(
<Route>
<Route component={Landing}>
<Route path='/login' component={Login}/>
<Route path='/register' component={Register}/>
</Route>
<Route path="admin" getComponent={(location, cb) => {
require.ensure([], require => {
cb(null, require('./containers/admin'))
})
}}/>
<Route component={App}>
<Route path='/' component={Home} />
</Route>
</Route>
)}
My component
class Admin extends Component {
componentDidMount() {
this.props.load()
}
render() {
const { message, isFetching } = this.props
return (
<div>
<p>{message}</p>
<p>This module was loaded via chunk </p>
{loading && <p>Doing some fake loading ...</p>}
</div>
)
}
}
Admin.propTypes = {
message: PropTypes.string.isRequired,
isFetching: PropTypes.bool.isRequired,
load: PropTypes.string.isRequired
}
const mapStateToProps = state => state.admin
function mapDispatchToProps(dispatch) {
return bindActionCreators({ load }, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Admin)
Does anyone have the same error? any ideas? Anyone have something similar working?
Thanks community!
Update: Added index.js for clarity
import configureRoutes from './routes'
import configureStore from './store/configureStore'
import coreReducers from './modules/core'
import ReducerRegistry from './reducer-registry'
var reducerRegistry = new ReducerRegistry(coreReducers)
// Configure hot module replacement for core reducers
if (process.env.NODE_ENV !== 'production') {
if (module.hot) {
module.hot.accept('./modules/core', () => {
var nextCoreReducers = require('./modules/core')
reducerRegistry.register(nextCoreReducers)
})
}
}
const routes = configureRoutes(reducerRegistry)
const store = configureStore(reducerRegistry)
render(
<I18nextProvider i18n={i18n}>
<Provider store={store}>
<Router history={browserHistory} routes={routes} />
</Provider>
</I18nextProvider>,
document.getElementById('root')
)

I think your root <Route> is missing the component field.
You need to specify either component or getComponent for every parent route, as this will be the component that the current child route’s component gets passed to as this.props.children.
Rather than
export default function configureRoutes(reducerRegistry) {
return (
<Route>
you want something like
export default function configureRoutes(reducerRegistry) {
return (
<Route component={App}>
In this case, you probably won’t need another App route below.

Related

Props object is empty with history.push

I have an onClick handler used for filtering data that routes back to the same component, but with a different url. However, when this same components renders again, I cannot access props.location.
Lots of code has been left out for brevity.
Component:
import { useHistory } from 'react-router-dom';
const Dashboard = (props) => {
const history = useHistory();
console.log(props) // Empty
useEffect(() => {
console.log(props) // Empty
})
const handleFilter = argument => {
history.push('/filter'); // 'argument' left out to test routing, and to ensure props.location is accessible
}
return (
<button onClick={() => handleFilter('someArgumentHere')}>Filter</button>
)
}
Router:
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Redirect,
} from 'react-router-dom';
const PrivateRoute = ({ exact, path, component: Component }) => {
return (
<Route
exact={exact}
path={path}
render={props => (
<div>
<Navbar />
<Component {...props} />
</div>
)}
/>
);
};
<Router>
<Switch>
<PrivateRoute exact component={Dashboard} path="/" />
<PrivateRoute exact component={Dashboard} path="/filteredPriority" />
</Switch>
</Router>
When I click on handleFilter, the routing works. Meaning, my browser navigates from '/' to '/filteredPriority', and I see the same content, which is desired. But, since this is a filter, I want to access the url params via props.location, and it's empty. I don't know why.
Figured it out. Needed to wrap my Dashboard component in withRouter.

How to access match outside of Route?

What is the best way to accomplish what I am trying in the code below? App cannot access match.params for routes defined inside it, but I would like to pass parts of the state to child components based on the url params. I cannot use hooks like useRouteMatch() because App is a stateful class component. I think I can do this with the the Route render method, but it looks like React Router docs say that method is deprecated.
So is there a design pattern similar to this that lets me keep all route logic in App and just pass props to child components based on params, that doesnt use the render method of Route?
class App extends React.Component {
state = { things: this.props.things };
render() {
return (
<Switch>
<Route path='/thing/:thingId'>
<OneThing thing={this.state.things.find(thing => thing.id === match.params.thingId)} />
</Route>
<Route path='/things/:thingTag'>
<MultipleThings things={this.state.things.filter(thing => thing.tag === match.params.thingTag)} />
</Route>
</Switch>
);
}
}
with <Route render>
<Route path='/thing/:thingId'
render={(route) => <OneThing thing={route.match.params.thingId} />} />
with <Route children> ver 5.1
<Route
path='/thing/:thingId'
children={({ match }) => (
<OneThing thing={match.params.thingId} />
)}
/>
try using withRouter
import React from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
// A simple component that shows the pathname of the current location
class OneThing extends React.Component {
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
render() {
const { match, location, history } = this.props;
return <div>You are now at {location.pathname}</div>;
}
}
// Create a new component that is "connected" (to borrow redux
// terminology) to the router.
export default withRouter(ShowTheLocation);

Migrating from React Router v2 to v4

So, I'm currently using react-router v2 as follows:
import { IndexRoute, Router, Route, Redirect } from 'react-router';
import App from './components/App';
....
render () {
return (
<ApolloProvider store={store} client={client}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={PhotoGrid} />
<Route path="/view/:postId" component={Single}></Route>
<Route path="/login" component={LoginUser}></Route>
</Route>
</Router>
</ApolloProvider>
)
}
}
export default MainApp;
App.js
....
import Main from './Main';
const allPostsCommentsQuery = graphql(All_Posts_Comments_Query, {
options: {
cachePolicy: 'offline-critical',
fetchPolicy: 'cache-first',
},
});
function mapStateToProps(state) {
return {
auth: state.auth
};
}
export const mapDispatchToProps = (dispatch) => {
return bindActionCreators(actionCreators, dispatch);
}
export default compose(
allPostsCommentsQuery,
connect(mapStateToProps, mapDispatchToProps)
)(Main);
Main.js
class Main extends React.Component {
constructor (props) {
super(props);
}
componentWillMount () {
if (!this.props.auth.token){
this.context.router.push('/login');
}
}
render () {
return (
<div>
<h1>
<Link to="/">Flamingo City</Link>
</h1>
{ React.cloneElement(this.props.children, this.props) }
</div>
);
}
}
Main.contextTypes = {
router: function() { React.PropTypes.func.isRequired }
};
export default Main;
How do I convert my current v2 router to v4? What I am not clear on, is the parent nested element:
<Route path="/" component={App}>
In all the v2 -> v4 conversion examples I have seen thus far, none clearly explain what happens to the child elements. Am I expected to place the child elements within the App.js component itself, and if so, in the version of my App.js, how would that work as the first sign of any navigation actually occurs with Main.js?
Really useful post on github where you can see all the important parts of migrating to v4.
https://gist.github.com/kennetpostigo/7eb7f30162253f995cd4371d85c1e196
Also explaining how to go about child routes. Basically, you are supposed to place a Match inside App.js so this parent component will become responsible for its own part of child routes, an so on with every parent component.
Haven't tried this, let me know how it goes!

Routing is not work with react-router-redux actions

When use the Link, router works as expected, though i get a warning [history] pushState is deprecated; use push instead.
Using routeActions from react-router-redux does't work, url was change (http://localhost:3002/addCity), but view still the same (Home) or show error if i go to page by url for example: localhost:3002/addCity.
git: https://github.com/kirsanv43/Weather.git
reducers:
export default combineReducers({
cities,
routing: routeReducer
});
store config: https://github.com/kirsanv43/Weather/blob/master/app/redux/config/store.js
import rootReducer from '../reducers'
export default function configureStore(initialState) {
const history = createHistory();
const middleware = syncHistory(history)
const finalCreateStore = compose(
applyMiddleware(middleware)
)(createStore)
const store = finalCreateStore(rootReducer)
middleware.listenForReplays(store);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers').default
store.replaceReducer(nextRootReducer)
})
}
return store
}
Router:
const store = configureStore()
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="addCity" component={AddCity}/>
</Route>
</Router>
</Provider>
,
document.getElementById('root')
);
Component:
class CitiesList extends React.Component {
onAddCity = () => {
this.props.route.push('/addCity')
};
render() {
return <div className="weatherContainer">
<ul>
<li className="listItem addBtn"><a onClick={this.onAddCity}><span>ADD CITY</span></a></li>
</ul>
</div>
}
}
function mapDispatchToProps(dispatch) {
return {
route:bindActionCreators(routeActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CitiesList)
insted createHistory need use createHashHistory from 'history'
that working for me
Please try to change "react-router-redux" version to "^5.0.0-alpha.8". It solved my problem

react router - Redirection after login

Could you please help me in understanding the redirection mechanism I could use with latest version of react router ( v1.1.0 ) . I would like to redirect to a url depending on the success or failure of user login .
I have tried to do the following
First created a history using.
let history = createBrowserHistory();
then tried to push the state using
history.pushState(null, 'abc')
Nothing is happening. Could you please let me know the correct way to do transitions .From the docs I understood that transitionTo() API is not present in the latest versions.
It will be great If you could point to a simple working example.
Wanted to update this thread because I spent a good amount of time digging around on this. In React Router 2.0.x, replaceState is deprecated in favor of replace. See here for details: https://github.com/ReactTraining/react-router/blob/v2.0.0/upgrade-guides/v2.0.0.md#link-to-onenter-and-isactive-use-location-descriptors
The correct way to do this would be something like this:
function requireAuth(nextState, replace) {
if (!userExists()) {
replace({
pathname: '/signin',
state: { nextPathname: nextState.location.pathname }
})
}
}
export const renderRoutes = () => (
<Router history={browserHistory}>
<Route path="protectedRoute" component={Protected} onEnter={requireAuth} />
<Route path="signin" component={SignIn} />
</Route>
</Router>
);
Then, in the SignIn component, you can redirect after a successful sign in like this:
signInFunction({params}, (err, res) => {
// Now in the sign in callback
if (err)
alert("Please try again")
else {
const location = this.props.location
if (location.state && location.state.nextPathname) {
browserHistory.push(location.state.nextPathname)
} else {
browserHistory.push('/')
}
}
})
You can register "hooks" on your routes that get triggered when you enter and leave the routes. Check out the documentation for onEnter and onLeave hooks.
There is also an example of requiring auth on a route and redirecting to a different path if the user is not logged in.
Here's a snippet taken from the require auth example within app.js:
function requireAuth(nextState, replaceState) {
if (!auth.loggedIn())
replaceState({ nextPathname: nextState.location.pathname }, '/login')
}
// And from the route configuration, use the requireAuth function in onEnter...
<Router history={history}>
<Route path="/" component={App}>
<Route path="login" component={Login} />
<Route path="logout" component={Logout} />
<Route path="about" component={About} />
<Route path="dashboard" component={Dashboard} onEnter={requireAuth} />
</Route>
</Router>
The nextState and replaceState arguments are objects from rackt/history and get injected into the method you pass into onEnter.
React Router v4.2
I am using React-16.2 & React-router-4.2
and I get solution by this
this.props.history.push("/");
My working code:
.then(response => response.json())
.then(data => {
if(data.status == 200){
this.props.history.push("/");
console.log('Successfully Login');
}
})
I was following this document redirect-on-login-and-logout
#terranmoccasin 's answer is correct. However there is a common need very few examples address.
Let's say you need to secure several routes (dashboard1, dashboard2, ...). How do you redirect back to the original page once you log in successfully? In other words, what do you do with {nextPathname: nextState.location.pathname}?
Here's what I do in ./containers/LoginContainer.js:
import { push } from 'react-router-redux';
const mapStateToProps = (state) => ({
nextPathname: state.routing.locationBeforeTransitions.state.nextPathname,
});
const mapDispatchToProps = (dispatch) => ({
changeLocationOnSignIn: (nextPathname) => {
dispatch(push(nextPathname));
},
});
and in ./components/Login.js
componentWillReceiveProps(nextProps) {
// user signed in or signed up, assuming redux. you may use this elsewhere.
if (nextProps.user.status === 'authenticated' && nextProps.user.user &&
!nextProps.user.error) {
this.props.changeLocationOnSignIn(this.props.nextPathname);
}
React-router 2.4.0 (April 2016) introduced withRouter which creates a HOC. However it wraps React.createClass, not a JS class. I haven't been able to get it working with redux-form, etc. Besides I think the above code is easier to comprehend.
i want just share the actual answer at 2020 year.
The main way for storing previous location in state is the same. But onEnter was removed from library. Now we can use AuthRoute as in the documentation:
<AuthRoute exact path="/food">
<Food />
</AuthRoute>
<Route exact path="/login">
<Login />
</Route>
const AuthRoute = ({ children, isAuthorized, ...rest }) => {
const loginLink = usePrepareLink({
to: "/login",
isRelativePath: true
});
return (
<Route {...rest} render={({ location }) =>
isAuthorized ? (
children
) : (
<Redirect to={{
...loginLink,
state: { from: location }
}} />
)
} />
);
};
and we can use the state to restore previouse URL after login
const onSignIn = useCallback(() => {
setIsAuthorized(value);
const link = (state && state.from) || "/restore-prevented-route";
history.replace(link);
}, [setIsAuthorized, value, history, state]);
The details you can find here (or RU)
This helps me.
Redirect to Login After Logout
import { useHistory } from "react-router-dom";
const history = useHistory();
history.push("/login");
onEnter no longer exists on react-router-4, You can make use of <Route render={ ... } /> to achieve the same functionality.
Here is an example of the same.
<React.Fragment>
<Switch>
<Route path="/dashboard" render={() => (isAuth() ? <Redirect to="/login" /> : <DashboardRoutes />)} />
<Route path="/login" component={Login} />
</Switch>
</React.Fragment>
isAuth() in my case is a function that basically check whether we have the auth token or not and returns true/false based on that.
function isLoggedIn() {
if (!localStorage.getItem('token')) {
return true;
}
return false;
}
As #JohnSz mentions I too had issues with using withRouter. Instead I did it as instructed here:
https://github.com/reactjs/react-router/blob/master/upgrade-guides/v2.0.0.md#programmatic-navigation
const RouteComponent = React.createClass({
contextTypes: {
router: React.PropTypes.object.isRequired
},
someHandler() {
this.context.router.push(...)
}
})
Basically:
Define contextType
Use this.context.router.push(...)
Cheers.
You can set a condition for the success and failure. Then use the useNavigate hook.
import { useNavigate } from "react-router-dom";
function useLogoutTimer() {
const userIsInactive = useFakeInactiveUser();
const navigate = useNavigate();
useEffect(() => {
if (userIsInactive) {
fake.logout();
navigate("/url");
}
}, [userIsInactive]);
}
Read more: https://reactrouter.com/en/main/hooks/use-navigate