migrate to react-router v4 - reactjs

I try to migrate to the react-router v4, where should I start? For most paths I need authentication and redicert. I installed last react-router-dom#next and react-router#next. Now when user is not login redirect to login page,
also I have login-callback, but have an error
WebSocket connection to 'ws://localhost:9998/sockjs-node/472/gnanejj3/websocket' failed: WebSocket is closed before the connection is established.
application.js
import React, {Component} from 'react'
import {connect} from 'react-redux'
import {IntlProvider} from 'react-intl'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import getMuiTheme from 'material-ui/styles/getMuiTheme'
import theme from './theme'
import * as AuthActions from './actions/auth'
import Routes from './routes'
export class Application extends Component {
static propTypes = {
locale: React.PropTypes.string.isRequired,
messages: React.PropTypes.objectOf(React.PropTypes.string.isRequired).isRequired,
login: React.PropTypes.func.isRequired,
redirectToLoginPage: React.PropTypes.func.isRequired,
isLoggedIn: React.PropTypes.bool.isRequired,
};
render = () =>
<IntlProvider locale={this.props.locale} messages={this.props.messages}>
<MuiThemeProvider muiTheme={getMuiTheme(theme)}>
<Routes />
</MuiThemeProvider>
</IntlProvider>;
}
export default Application
routes.js:
import React from 'react'
import {connect} from 'react-redux'
import {BrowserRouter, Route, Link ,Redirect, withRouter, Switch} from 'react-router-dom'
import Root from './components/root'
import Home from './components/homePage'
import Devices from './components/devices'
import Users from './components/users'
import AddOrEditUser from './components/users/addOrEdit'
import Plans from './components/plans'
import ServerSettings from './components/settings'
import About from './components/aboutPage'
import Patients from './components/patients'
import AddOrEditPatient from './components/patients/addOrEdit'
import Commissioning from './components/commissioning'
import * as AuthActions from './actions/auth'
import redirectToLoginPage from './api/auth'
const Routes = ({isLoggedIn, redirectToLoginPage, login}) =>
<BrowserRouter>
<Switch>
<Route path='/' render={({location}) => {
if (!isLoggedIn)
redirectToLoginPage()
return (
<Root>
<Switch>
<Route path='/home' component={Home} />
<Route path='/devices' component={Devices} />
<Route path='/users' component={Users} />
<Route path='/users/add' component={AddOrEditUser}/>
<Route path='/users/edit/:personId' component={AddOrEditUser}/>
<Patients path='/patients' render={() =>
<Switch>
<Route path='/add' component={AddOrEditPatient} />
<Route path='/edit/:personId' component={AddOrEditPatient} />
</Switch>
}
/>
<Route path='/plans' component={Plans} />
<Route path='/commissioning' component={Commissioning} />
<Route path='/server-settings' component={ServerSettings} />
<Route path='/about' component={About} />
</Switch>
</Root>)
}}
/>
<Route path='/login-callback' render={() => {
if (isLoggedIn)
login()
return <Root />
}}
/>
</Switch>
</BrowserRouter>
Routes.propTypes = {
// authenticate: React.PropTypes.func.isRequired,
// processLoginCallback: React.PropTypes.func.isRequired,
login: React.PropTypes.func.isRequired,
redirectToLoginPage: React.PropTypes.func.isRequired,
isLoggedIn: React.PropTypes.bool.isRequired,
}
export const mapDispatchToProps = dispatch => ({
login: () => dispatch(AuthActions.login()),
redirectToLoginPage: () => dispatch(AuthActions.redirectToLoginPage()),
})
export const mapStateToProps = state => ({
isLoggedIn: state.auth.isLoggedIn,
locale: state.intl.locale,
messages: state.intl.messages,
})
export default connect(mapStateToProps, mapDispatchToProps)(Routes)

That's a complex question for which a StackOverflow response box is too limiting. I've written a lengthy migration guide. If you're comfortable with react-router v2:v3, this will be a good resource for your migration:
https://medium.com/#francoisz/react-router-v4-unofficial-migration-guide-5a370b8905a

Since v4 does not support nested route, this code
<Route path='/' render={({location}) => {
if (!this.props.isLoggedIn)
this.props.redirectToLoginPage()
return ( ...
will probably stop working
I get around by creating some wrappers for the components .e.g.
const enforceLoginValidation = (children) => {
const fn = ({ match }) => {
if (!this.props.isLoggedIn) {
this.props.redirectToLoginPage()
return
}
// to pass react-router's match to the children
const extendedChild = React.cloneElement(children, { match });
return extendedChild;
};
return fn;
};
...
const HomePage = enforceLoginValidation(
<Home />
);
....
<Route path='/home' component={HomePage} />

Related

React-Redux-Saga history.push is updating url, but not loading the component

I am trying to redirect from saga after a password change, and history.push() is just updating the URL not loading the component
Hisotry.js
import { createBrowserHistory as history } from "history";
export default history({
bforceRefresh: true,
});
App.js
import React from "react";
import { BrowserRouter } from "react-router-dom";
import history from "../../helpers/history";
import Navigation from "../Navigation";
import AppAuthRouter from "../Navigation/routes";
const App = () => (
<BrowserRouter history={history}>
<Navigation app_auth_router={AppAuthRouter} />
</BrowserRouter>
);
export default App;
AppAuthRouter.js
import * as ROUTES from "../../constants/routes";
import ProtectedRoute from "./protectedroute";
const AppAuthRouter = () => (
<Switch>
<ProtectedRoute path="/admin" component={AdminPage} />
<ProtectedRoute
path="/updatepwd"
component={PasswordChangePage}
/>
<ProtectedRoute path="/welcome" component={WelcomePage} />
<Route path={"/signup} component={SignUpPage} />
<Route path={"/signin} component={SignInPage} />
<Redirect path="*" to={{ pathname:"/signin"}} />
</Switch>
);
export default AppAuthRouter;
Saga.js
import history from "../helpers/history";
function* passwordChangeSaga({ creds }) {
try {
yield put(alertSuccess("Password has been changed successfully"));
history.push("/welcome");
} catch (gerror) {
const error = googleError2Error(gerror);
}
}

React-Router PrivateRoute is not working. What am I missing?

I've got react-redux private router not working.
It is just showing me a blank page of the route I am trying to access.
And my states are not loading at Redux Dev Tools.
Can it also be the problem of HashRouter?
My PrivateRoute Component:
import React from "react";
import { connect } from "react-redux";
import {Router, Redirect, withRouter } from "react-router-dom";
import PropTypes from 'prop-types';
class PrivateRoute extends React.Component {
static propTypes = {
isAuthenticated: PropTypes.bool,
}
isLoggedin = () => {
return this.props.isAuthenticated;
};
render = () => {
let { component: Component, ...rest } = this.props;
return (
<Router
{...rest}
render={(props) =>
this.isLoggedin() ? (
<Component {...props} />
) : props.location.pathname === "/" ? null : (
<Redirect to="/" />
)
}
/>
);
};
}
const mapStateToProps = (state) => ({
auth: state.auth,
isAuthenticated: state.auth.isAuthenticated
})
export default withRouter(connect(mapStateToProps, null)(PrivateRoute));
My App where I am wrapping my Routes to make it private:
import {Provider} from 'react-redux';
import store from './store'
import {loadUser} from './actions/authActions'
import PrivateRoute from './components/auth/PrivateRoute'
class App extends React.Component {
componentDidMount(){
store.dispatch(loadUser())
}
render(){
return (
<Provider store={store}>
<HashRouter basename="/">
<Navbar />
<Route exact path="/" component={Home}/>
<PrivateRoute path="/aeons" component={AeonsList} />
<PrivateRoute path="/carrefours" component={CarrefoursList} />
<PrivateRoute path="/farmers" component={FarmersList} />
<PrivateRoute path="/foodhalls" component={FoodhallsList} />
<PrivateRoute path="/grands" component={GrandsList} />
<PrivateRoute path="/heros" component={HerosList} />
<PrivateRoute path="/primos" component={PrimosList} />
<PrivateRoute path="/ranchos" component={RanchsList} />
</HashRouter>
</Provider>
)
}
}
export default App;
You need to import Route, not Router when defining your privateRoute component.
import {Route } from "react-router-dom";
Check this

Rect Js router stop when routes import from external file

i working by react js router (react-router-dom v4.2) and Redux together and have strange varios problem
first i say i successfully used and run this but the problems
when i use the root route as first rote in route list router stop working!!
but when i use '/' route as last route everything is ok
<Switch>
<Route path="/blog" component={Blog} />
<Route path="/users/" component={Users} />
<Route path="/" component={Home} axact />
</Switch>
when i create routes as external component and import it to app routes not work
this is my route.js codes
import React, { Component } from 'react';
import { Switch, Route } from "react-router-dom";
// import Components
import Home from './components/Home'
import Blog from './components/blog/Blog'
import Users from './components/users/Users'
class router extends Component {
render() {
return (
<div>
<Switch>
<Route path="/" component={Home} axact />
<Route path="/blog" component={Blog} />
<Route path="/users/" component={Users} />
</Switch>
</div>
);
}
}
export default router;
this is my App.js codes:
import React, { Component } from 'react';
import { getUsers } from './store/actions'
import { connect } from 'react-redux'
import { withRouter } from "react-router-dom";
import Nav from './components/navigation/Nav';
import axios from 'axios';
const userApi = "https://jsonplaceholder.typicode.com/users"
class App extends Component {
componentWillMount() {
axios.get(userApi)
.then(response => {
let users = response.data
this.props.getUserList(users)
})
.catch(error => {
console.log(error)
})
}
render() {
return (
<div className="container">
<Nav />
<Router />
</div>
);
}
}
const mapDispatchToProps = dispatch => ({ getUserList: (api) => dispatch(getUsers(api)) })
export default withRouter(connect(null, mapDispatchToProps)(App));
when i use this codes everything is ok
import React, { Component } from 'react';
import { getUsers } from './store/actions'
import { connect } from 'react-redux'
import { Switch, Route, withRouter } from "react-router-dom";
import Home from './components/Home'
import Blog from './components/blog/Blog'
import Users from './components/users/Users'
import Nav from './components/navigation/Nav';
import axios from 'axios';
const userApi = "https://jsonplaceholder.typicode.com/users"
class App extends Component {
componentWillMount() {
axios.get(userApi)
.then(response => {
let users = response.data
this.props.getUserList(users)
})
.catch(error => {
console.log(error)
})
}
render() {
return (
<div className="container">
<Nav />
<Switch>
<Route path="/blog" component={Blog} />
<Route path="/users/" component={Users} />
<Route path="/" component={Home} axact />
</Switch>
</div>
);
}
}
const mapDispatchToProps = dispatch => ({ getUserList: (api) => dispatch(getUsers(api)) })
export default withRouter(connect(null, mapDispatchToProps)(App));

React Private Route higher order component?

I am trying to make a PrivateRoute component for react. Here is my higher order component. Can you tell me what is the problem with this.
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
export default ({ component: Component, ...rest }) => {
class PrivateRoute extends React.Component {
render() {
console.log("This is private route called");
if (this.props.profile) {
return (
<Route
{...rest}
render={props =>
this.props.profile.loggedIn === true ? (
<Component {...props} />
) : (
<Redirect to="/login" />
)
}
/>
);
}
}
}
const mapStateToProps = state => ({
profile: state.profile
});
return connect(mapStateToProps)(PrivateRoute);
};
Here's how you can accomplish a protected route via a protected route component.
Working example: https://codesandbox.io/s/yqo75n896x
containers/RequireAuth.js
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import ShowPlayerRoster from "../components/ShowPlayerRoster";
import ShowPlayerStats from "../components/ShowPlayerStats";
import Schedule from "../components/Schedule";
const RequireAuth = ({ match: { path }, isAuthenticated }) =>
!isAuthenticated ? (
<Redirect to="/signin" />
) : (
<div>
<Route exact path={`${path}/roster`} component={ShowPlayerRoster} />
<Route path={`${path}/roster/:id`} component={ShowPlayerStats} />
<Route path={`${path}/schedule`} component={Schedule} />
</div>
);
export default connect(state => ({
isAuthenticated: state.auth.isAuthenticated
}))(RequireAuth);
routes/index.js
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import Home from "../components/Home";
import Header from "../containers/Header";
import Info from "../components/Info";
import Sponsors from "../components/Sponsors";
import Signin from "../containers/Signin";
import RequireAuth from "../containers/RequireAuth";
import rootReducer from "../reducers";
const store = createStore(rootReducer);
export default () => (
<Provider store={store}>
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/info" component={Info} />
<Route path="/sponsors" component={Sponsors} />
<Route path="/protected" component={RequireAuth} />
<Route path="/signin" component={Signin} />
</Switch>
</div>
</BrowserRouter>
</Provider>
);
Or, if you want something that wraps all routes (instead of having to specify a protected route component). Then you can do something like the below.
Working example: https://codesandbox.io/s/5m2690nn6n
components/RequireAuth.js
import React, { Component, Fragment } from "react";
import { withRouter } from "react-router-dom";
import Login from "./Login";
import Header from "./Header";
class RequireAuth extends Component {
state = { isAuthenticated: false };
componentDidMount = () => {
if (!this.state.isAuthenticated) {
this.props.history.push("/");
}
};
componentDidUpdate = (prevProps, prevState) => {
if (
this.props.location.pathname !== prevProps.location.pathname &&
!this.state.isAuthenticated
) {
this.props.history.push("/");
}
};
isAuthed = () => this.setState({ isAuthenticated: true });
unAuth = () => this.setState({ isAuthenticated: false });
render = () =>
!this.state.isAuthenticated ? (
<Login isAuthed={this.isAuthed} />
) : (
<Fragment>
<Header unAuth={this.unAuth} />
{this.props.children}
</Fragment>
);
}
export default withRouter(RequireAuth);
routes/index.js
import React from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Home from "../components/Home";
import Players from "../components/Players";
import Schedule from "../components/Schedule";
import RequireAuth from "../components/RequireAuth";
export default () => (
<BrowserRouter>
<RequireAuth>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/players" component={Players} />
<Route path="/schedule" component={Schedule} />
</Switch>
</RequireAuth>
</BrowserRouter>
);
Or, if you want something a bit more modular, where you can pick and choose any route, then you can create a wrapper HOC. See this example (while it's written for v3 and not for authentication, it's still the same concept).
It looks like your render function's only return is inside of an if block, so its returning null. You need to fix the logic to just return the Route and make profile a required prop in your proptypes check instead of using an if block.
PrivateRoute.propTypes = {
profile: PropTypes.object.isRequired
};

React Router - Path component is not rendering

I'm working on building a simple hacker news app using React and Redux and I'm having trouble with rendering an individual article page component (the ArticleSingle components).
The routing works fine, but the ArticleSingle component does not render on click.
Here's my Root file, which handles all of the routing:
// REACT
import React from 'react';
import PropTypes from 'prop-types';
// REACT ROUTER
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
// REDUX
import { Provider } from 'react-redux';
import createBrowserHistory from 'history/createBrowserHistory';
const history = createBrowserHistory();
// COMPONENTS
import AppContainer from './components/containers/AppContainer';
import Home from './components/presentational/Home';
import ArticleSingle from './components/presentational/ArticleSingle';
const Root = ({ store }) => (
<Provider store={store}>
<Router history={history}>
<Switch>
<Route path="/" component={AppContainer} />
<Route path="/" component={Home} />
<Route path="/topics/:topic_name/articles" component={Home} />
<Route path="/articles/:article_id" component={ArticleSingle} />
</Switch>
</Router>
</Provider>
);
Root.propTypes = {
store: PropTypes.object.isRequired
};
export default Root;
Here's the ArticleSingle component, which is the one that I'm having trouble with. I ultimately want to get it to pass the id from the URL to an action passed down from AppContainer, but I was hoping to get the component to render something on the page (the id) before starting the redux work.
import React from 'react';
import PropTypes from 'prop-types';
const ArticleSingle = ({ params, children }) => (
<div>
{params.article_id}
{children}
</div>
);
export default ArticleSingle;
Here's the Home component, it features a filter bar, what why there are two routes for it
import React from 'react';
import PropTypes from 'prop-types';
// Components
import Spinner from 'react-spinkit';
import ArticleList from './ArticleList';
import TopicsSubNav from './TopicsSubNav';
const Home = ({
loading,
articles,
topics,
fetchTopicArticles,
fetchArticles,
children
}) => {
return (
<div>
<h3 className="title is-3">Northcoders News</h3>
<div id="TopicsSubNav">
{loading && <Spinner name="pacman" color="coral" fadeIn="none" />}
<TopicsSubNav
topics={topics}
onTopicClick={fetchTopicArticles}
onAllClick={fetchArticles}
/>
</div>
{loading && <Spinner name="pacman" color="coral" fadeIn="none" />}
<ArticleList articles={articles} topics={topics} />
{ children }
</div>
);
};
Home.propTypes = {
articles: PropTypes.array.isRequired,
topics: PropTypes.array.isRequired,
loading: PropTypes.bool,
fetchTopicArticles: PropTypes.func.isRequired,
fetchArticles: PropTypes.func.isRequired
};
export default Home;
Here's the AppContainer, controls the state for all of the app.
import React from 'react';
import PropTypes from 'prop-types';
import Home from '../presentational/Home';
import ArticleSingle from '../presentational/ArticleSingle';
// Redux
import { connect } from 'react-redux';
import * as actions from '../../actions/actions';
class AppContainer extends React.Component {
componentDidMount () {
this.props.fetchArticles();
this.props.fetchTopics();
}
render () {
return (
<div>
<Home
articles={this.props.articles}
topics={this.props.topics}
loading={this.props.loading}
fetchTopicArticles={this.props.fetchTopicArticles}
fetchArticles={this.props.fetchArticles}
/>
</div>
);
}
}
function mapDispatchToProps (dispatch) {
return {
fetchArticles: () => {
dispatch(actions.fetchArticles());
},
fetchTopics: () => {
dispatch(actions.fetchTopics());
},
fetchTopicArticles: topic => {
dispatch(actions.fetchTopicArticles(topic));
},
fetchArticleById: id => {
dispatch(actions.fetchArticleById(id));
}
};
}
function mapStateToProps (state) {
return {
articles: state.articles.data,
article: state.article,
topics: state.topics.data,
loading: state.loading
};
}
AppContainer.propTypes = {
articles: PropTypes.array.isRequired,
topics: PropTypes.array.isRequired,
loading: PropTypes.bool,
fetchArticles: PropTypes.func.isRequired,
fetchTopics: PropTypes.func.isRequired,
fetchTopicArticles: PropTypes.func.isRequired,
fetchArticleById: PropTypes.func.isRequired
};
export default connect(mapStateToProps, mapDispatchToProps)(AppContainer);
I see a couple of issues in your code.
1) Lets say you have <Route path="/articles/:article_id" component={ArticleSingle} /> - To access the article_id in ArticleSingle Componenet you need to use props.match.params.article_id
2) This is your route:
<Route path="/" component={AppContainer} />
<Route path="/" component={Home} />
<Route path="/topics/:topic_name/articles" component={Home} />
<Route path="/articles/:article_id" component={ArticleSingle} />
Basically when you visit /articles/article-id you are matching the first route <Route path="/" component={AppContainer} /> and you are getting into it.
To solve this issue you can do one of the following:
A. use exact in your first two routes. That would be <Route exact path="/" component={AppContainer} /> and <Route exact path="/" component={Home} />
B. Change the order of your routes and but the homepage root at the bottom like the following:
<Route path="/topics/:topic_name/articles" component={Home} />
<Route path="/articles/:article_id" component={ArticleSingle} />
<Route path="/" component={AppContainer} />
<Route path="/" component={Home} />
For the above solutions, I am assuming you are using React Router 4. I got the information I provided you from this tutorial:
https://medium.com/#pshrmn/a-simple-react-router-v4-tutorial-7f23ff27adf

Resources