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
Related
Goal:
When you write url "https://react-router-with-params-aq6utk.stackblitz.i/test" I would like to display the page test.
Problem:
Is it possble to retrieve the value 'test' and use it at app.js?
Today it doesn't work.
Info:
*Newbie in reactjs
Stackblitz:
https://stackblitz.com/edit/react-uaofyx?file=src%2FApp.js
app.js
import React, { Component } from 'react';
import { HomeComponent } from './Containers/HomeComponent';
import DashboardComponent from './Containers/DashboardComponent';
import ContactComponent from './Containers/ContactComponent';
import { Switch, Route, Link } from 'react-router-dom';
class App extends Component {
constructor() {
super();
this.state = {
name: 'React',
};
}
render() {
console.log(this.props.id);
console.log(this.props);
if (this.props.id === 'test') {
return (
<div>
<Switch>
<Route path="/test" exact component={ContactComponent} />
</Switch>
</div>
);
} else {
return (
<div>
<Switch>
<Route
path="/dashboard/:id"
render={(props) => (
<DashboardComponent {...props} isAuthed={true} />
)}
/>
<Route path="" exact component={HomeComponent} />
</Switch>
</div>
);
}
}
}
export default App;
ContactComponent.js
import React from 'react';
export const ContactComponent = ({ value }) => {
const name = 'CONTACT';
return <h1>{name}</h1>;
};
DashboardComponent.js
import React, { Component } from 'react';
class DashboardComponent extends Component {
constructor(props) {
super(props);
console.log(this.props.match.params.id);
}
render() {
return <div>Hello from dashboard.. ffff</div>;
}
}
export default DashboardComponent;
HomeComponent.js
import React from 'react';
export const HomeComponent = ({ value }) => {
const name = 'rajesh';
return <h1>{name}</h1>;
};
There are some issues with the implementaion of Route. also, you export the ContactComponent without default but you used it in App.js with the default import statement.
The working code:
import React, { Component } from 'react';
import { HomeComponent } from './Containers/HomeComponent';
import DashboardComponent from './Containers/DashboardComponent';
import { ContactComponent } from './Containers/ContactComponent';
import { Switch, Route, Link, BrowserRouter as Router } from 'react-router-dom';
class App extends Component {
render() {
return (
<Router>
<Switch>
<Route path="/test" exact component={ContactComponent} />
<Route
path="/dashboard/:id"
component={(props) => <DashboardComponent {...props} isAuthed={true} />}
/>
<Route path="" exact component={HomeComponent} />
</Switch>
</Router>
);
}
}
export default App;
check the live version on stackblitz
Explanation:
If you export a variable with the default keyword, so you need to import it without {}
const first = 'first'
const second = 'second'
export first
export default second
Now in usage:
import second, {first} from 'myVariables';
Also, your route configuration with react-router-dom will look like this:
<BrowserRouter>
<Switch>
<Route path="/" exact={true} component={Home} />
<Route path="/about" exact={true} component={About} />
<Route path="/contact" exact={true} component={Contact} />
// rest of the routes ...
</Switch>
</BrowserRouter>
Note: Your App.js component is the root component so there aren't any props with it.
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
I can’t understand why does not go to the page, but simply changes the URL.
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Link, withRouter } from 'react-router-dom'
import classify from 'src/classify'
import defaultClasses from './logo.scss'
import logo from './logo.svg'
class Logo extends Component {
static propTypes = {
classes: PropTypes.shape({
wrapper: PropTypes.string,
logo: PropTypes.string
})
}
render() {
const { classes, history } = this.props
return (
<Link to="/" className={classes.wrapper}>
<img className={classes.logo} src={logo} height="70" alt="" title="" />
</Link>
)
}
}
export default classify(defaultClasses)(Logo)
there is same withwithRouter() history.push
The component don't rendering.
In renderRoutes() i have next path
<Route exact path="/" component={Page} />
renderRoutes() call from App.js(main)
import React from 'react'
import { Switch, Route } from 'react-router-dom'
import Page from '../../Page'
import Journey from 'src/components/Journey'
const renderRoutingError = props => <ErrorView {...props} />
const renderRoutes = () => (
<Switch>
<Route exact path="/" component={Page} />
<Route exact path="/journey/" component={Journey} />
)
export default renderRoutes
use Context
this.props.pushContext({
nav: <Logo />,
background: 'white'
})
You forgot to close your Switch component in renderRoutes.
const renderRoutes = () => (
<Switch>
<Route exact path="/" component={Page} />
<Route exact path="/journey/" component={Journey} />
</Switch> <-- You forgot to close your Switch here.
)
It seems my application will not render the component passed to <Route /> unless I refresh the page. What could I be doing wrong?
components/App/index.jsx
// dependencies
import React from 'react';
import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom'
// components
import Header from '../Header';
// containers
import SidebarContainer from '../../containers/SidebarContainer';
import MainContainer from '../../containers/MainContainer';
const App = ({store}) => (
<Provider store={store}>
<Router>
<div className="wrapper">
<Header />
<div className="container-fluid container-fluid--fullscreen">
<div className="row row--fullscreen">
<SidebarContainer />
<MainContainer />
</div>
</div>
</div>
</Router>
</Provider>
);
App.propTypes = {
store: PropTypes.object.isRequired,
};
export default App;
containers/MainContainer.jsx
// dependencies
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Route } from 'react-router-dom'
// components
import Dashboard from '../components/Dashboard';
import List from '../components/List';
// containers
import LoginContainer from './LoginContainer.jsx'
class Main extends Component {
render() {
console.log(this.props)
return(
<div className="wrapper">
<Route exact path="/" component={Dashboard} />
<Route path="/login" component={LoginContainer} />
<Route path="/users" component={List} />
</div>
);
}
}
const mapStateToProps = (state) => {
return {
token: state.authentication.token,
};
};
const MainContainer = connect(mapStateToProps, null)(Main);
export default MainContainer;
So it seems when I click on a <Link to="/users" /> component my path changes to http://localhost:3000/users but the component does not change from Dashboard to List
I'm also noticing that when I console.log this.props from MainContainer I do not see anything related to router such as this.props.location.pathname --perhaps I'm not structuring my application correctly?
After poking around the react-router issues page on github I found this thread: https://github.com/ReactTraining/react-router/issues/4671
It appears as though the redux connect method blocks context which is required by react-router package.
That being said, the fix for this is to wrap all redux connected components that have router components inside with withRouter() like so:
containers/MainContainer.jsx
// dependencies
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Route, withRouter } from 'react-router-dom' // IMPORT withRouter
// components
import Dashboard from '../components/Dashboard';
import List from '../components/List';
// containers
import LoginContainer from './LoginContainer.jsx'
class Main extends Component {
render() {
console.log(this.props)
console.log(this.context)
return(
<div className="wrapper">
<Route exact path="/" component={Dashboard} />
<Route path="/login" component={LoginContainer} />
<Route path="/users" component={List} />
</div>
);
}
}
const mapStateToProps = (state) => {
return {
token: state.authentication.token,
};
};
// WRAP CONNECT METHOD
const MainContainer = withRouter(connect(mapStateToProps, null)(Main));
export default MainContainer;
I think you have to do little more tweak in your code to make it work. Assuming you use react-router v4, the following should solve your problem.
import { BrowserRouter, Route, Switch } from 'react-router-dom';
<Provider store={store}>
<BrowserRouter>
<div>
<Switch>
<SidebarContainer />
<MainContainer />
</Switch>
</div>
</BrowserRouter>
</Provider>
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} />