I'm trying to pass a dynamic state to all the routes in a React router, specifically a shopping cart (an array of objects).
The layout is I have a parent component which contains the router and all the routes, and in that I want to store the cart in state and pass it to the routes (so essentially all routes will have access to it). I've been trying a few different things and troubleshooting it by looking it up on forums for a while but I just can't get it. This is the latest setup I have:
- Main.jsx
// This is the app entry point
import React, { Component } from 'react';
import { render } from 'react-dom';
import RouterHub from './RouterHub.jsx';
render((
<RouterHub />
), document.getElementById('root'));
- RouterHub.jsx
import React, { Component } from 'react';
import { render } from 'react-dom';
import { Router, Route, hashHistory } from 'react-router'
import Home from './Home.jsx';
import Dogs from './Pages/Other.jsx';
class RouterHub extends Component {
constructor(props) {
super(props);
this.addItem = this.addItem.bind(this);
this.state = {
cart: []
};
}
addItem(item) {
let newCart = this.state.cart.splice();
newCart.push(item);
this.setState({cart: newCart});
}
render() {
return(
<Router history={hashHistory}>
<Route path="/" component={Home} cart={this.state.cart} addItem={this.addItem} />
<Route path="/other" component={Other} cart={this.state.cart} addItem={this.addItem}/>
</Router>
);
}
}
export default RouterHub;
- Home.jsx
import React, { Component } from 'react';
import Slideshow from './Home/Slideshow.jsx';
import Navbar from './Constants/Navbar.jsx';
import Footer from './Constants/Footer.jsx';
class Home extends Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
<button onClick={() => this.props.route.addItem('potato')}>click me</button>
<Navbar />
// All the JSX content, I've removed to make it succint
<Footer />
</div>
);
}
}
export default Home;
Essentially what I'm wanting is in Home.jsx, when I click that button, I want another potato added to the cart. However, with this setup I get the error:
bundle.js:46451 Warning: [react-router] You cannot change <Router routes>; it will be ignored
How do I get it so that updating state in the RouterHub passes that to the routes, or is that not possible and I'm doing this all the wrong way?
Thanks for any help
Since you already have a main component for holding your state, you should insert that in the top level Route component something like this:
render((
<Router history={browserHistory}>
<Route path="/" component={RouterHub}>
<Route path="home" component={Home}/>
</Route>
</Router>
), document.getElementById('root'))
Then in your RouterHub component, pass those clone each children components with props, something like this:
{
React.Children.map( this.props.children, (child) => {
return React.cloneElement(child, this.props)
})
}
Bumping into this kind of problems will make you think of using some state management libraries like Redux/Flux.
Related
I am attempting to pass data via react-router-dom, specifically I wanted to hold state data in the App.js file which I am using to route to different pages. I can't get the props to pass. What am I doing off here? Below is an example of what I am trying to do:
App.js
import React, { Component } from 'react';
import Home from './Home';
import {BrowserRouter as Router, Route,Switch, withRouter } from 'react-router-dom';
class App extends Component {
constructor(props) {
super(props);
this.state = {
testProps:7
}
}
render() {
return (
<Router>
<div>
<Route
exact path="/"
component = {Home}
render={(props) => <Home testProps={this.state.testProps} {...props} />}/>
</div>
</Router>
);
}
}
export default App;
Home.js
import React, { Component } from 'react';
class Home extends Component {
render() {
return (
<div>
{`passing props from state: ${this.props.testProps}`}
</div>
);
}
}
export default Home;
In my home page I see: passing props from state: undefined. Am I approaching this incorrectly?
I have to combine use of Redux and React Router.
I tried react Router alone first and when I was clicking my images I was correctly redirected.
I followed redux tutorial and now when I click my images, I change the address (ex: http://localhost:3000/contact) but nothing displays as if the component was empty.
Root.js
import React from 'react';
import './index.css';
import PropTypes from 'prop-types'
import ReactDOM, { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom'
import App from './App'
import Users from './users'
import Book from './Book'
import Notfound from './notfound'
import { Provider } from 'react-redux';
import Store from './redux/Store/store'
import * as serviceWorker from './serviceWorker';
const Root = ({ store }) => (
<Provider store = { Store }>
<Router>
<div>
<Switch>
<Route exact path="/:filter?" component={App} />
<Route path="/users" component={Users} />
<Route path="/book" component={Book} />
<Route path='/manual' component={() => { window.location = 'https://------'; return null;} }/>
<Route path='/contact' component={() => { window.location = 'https://-------'; return null;} }/>
<Route component={Notfound} />
</Switch>
</div>
</Router>
</Provider>
)
Root.propTypes = {
store: PropTypes.object.isRequired
}
serviceWorker.unregister();
export default Root
index.js:
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import myReducer from './redux/Reducers/myReducer'
import Root from './Root'
const store = createStore(myReducer)
render(<Root store={store} />, document.getElementById('root'))
App.js:
import React from 'react'
import { Route, Link, Redirect, withRouter, BrowserRouter as Router } from 'react-router-dom'
import logo from './images/logo.png';
import book from './images/book.png';
class App extends React.Component {
constructor() {
super();
this.state = {
date: new Date()
};
}
render() {
const { date } = this.state;
return (
<div>
<img src={logo} />
<img src={book} onClick={() => this.props.history.push('/book')}/>
<img src={call} onClick={() => this.props.history.push('/contact')}/>
</div>
)
}
}
export default App
Do you know what is wrong ?
A few things I noticed:
When using react router you shouldn't use window.location to redirect since this reloads the whole page. The <Redirect> component from react-router is a better choice here.
Also you shouldn't use the component prop on the <Route>-component for things that aren't actually components, as there's the render prop for that (more on that here).
Furthermore: <Route exact path="/:filter?" component={App} /> is not going to work since :filter? is looking for a variable and exact is looking for an exact match. Moreover you probably shouldn't put the flexible one first since it's going to match every route that you throw at it. So all the following routes are practically unreachable.
I have a categories index page which links to a products index page of products specific to that category. That much is functioning. But when I attempt to click on a product linked to a show component specific to that product I'm encountering trouble. Below is my code:
router.js
import React from 'react';
import { Router, Route, Switch } from 'react-router';
import createBrowserHistory from 'history/createBrowserHistory'
import App from './App';
import CategoriesIndexPage from './pages/categories/CategoriesIndexPage';
import ProductsIndexPage from './pages/products/ProductsIndexPage';
import ProductShow from './pages/products/ProductShow';
import LocationsPage from './pages/LocationsPage';
const history = createBrowserHistory()
const router = (
<Router history={history}>
<Switch>
<Route exact path='/' component={App}/>
<Route path='/categories' component={CategoriesIndexPage}/>
<Route path='/locations' component={LocationsPage}/>
<Route path='/:category' component={ProductsIndexPage}>
<Route path='/:id' component={ProductShow}/>
</Route>
</Switch>
</Router>
);
export default router;
ProductIndexPage.js
import React, { Component } from 'react';
import { BWReactData } from '../../config/FirebaseConstants.js';
import Head from '../../components/Head.js';
import Foot from '../../components/Foot.js';
import ProductsIteration from './ProductsIteration';
class ProductsIndexPage extends Component {
constructor(props){
super(props);
this.state = {
allProducts: [],
loading: true,
}
}
componentDidMount() {
...
}
render() {
let allProducts = this.state.allProducts;
let loading = this.state.loading;
let categoryURL = this.props.location.state.category;
return (
<div>
<Head/>
<ProductsIteration
allProducts={allProducts}
loading={loading}
categoryURL={categoryURL}
/>
<Foot/>
</div>
)
}
}
export default ProductsIndexPage;
ProductsIteration.js
import React from 'react';
import { Link } from 'react-router-dom';
import { Col, Row } from 'react-materialize';
const ProductsIteration = props => {
let category = props.categoryURL;
if (props.loading) {
return <div>Loading...</div>
}
return (
<Row>
{props.allProducts.map(function(object) {
return (
<Col s={12} m={6} l={3} key ={object.id}>
<div style={styles.wrapper}>
<Link to={{ pathname: `${category}/${object.id}`, state: { id: object.id }}}>
<img src={object.img} style={styles.image} />
<div style={styles.description}>
<div style={styles.descriptionContent}>{object.name}</div>
</div>
</Link>
</div>
</Col>
)
})}
</Row>
)
}
export default ProductsIteration;
The link within my iteration component renders the '/:category/:id' url in my navbar but the page does nothing. This is my first project using router and any guidance would be much appreciated.
In React Router v4:
Router components are imported from 'react-router-dom' rather than 'react-router'.
The traditional <Router/> component has been replaced with the <BrowserRouter/> component, which requires no props.
Nesting routes is no longer convention. Instead, you'll have to nest your <ProductShow/> as a component prop of a <Route/> component within a <Switch/> component within your <ProductIndexPage/> component.
See below for an example.
Router.js:
// React.
import React from 'react'
// React Router DOM.
import {
BrowserRouter as Router,
Route,
Switch
} from 'react-router-dom'
// Routes.
import App from './App'
import CategoriesIndexPage from './pages/categories/CategoriesIndexPage'
import ProductsIndexPage from './pages/products/ProductsIndexPage'
import LocationsPage from './pages/LocationsPage'
// Router.
const Router = (
<Router>
<Switch>
<Route exact path='/' component={App}/>
<Route path='/categories' component={CategoriesIndexPage}/>
<Route path='/locations' component={LocationsPage}/>
<Route path='/:category/:id?' component={ProductsIndexPage}/>
</Switch>
</Router>
)
// Export.
export default Router
ProductIndexPage.js:
// React.
import React from 'react'
// BW React Data.
import {
BWReactData
} from '../../config/FirebaseConstants.js'
// Head.
import Head from '../../components/Head.js'
// Foot.
import Foot from '../../components/Foot.js'
// Products Iteration.
import ProductsIteration from './ProductsIteration'
// Product Show.
import ProductShow from './ProductShow'
// React Router DOM.
import {
Switch
} from 'react-router-dom'
// Products Index Page.
class ProductsIndexPage extends React.Component {
// Constructor.
constructor(props){
// Super Props.
super(props)
// State.
this.state = {
allProducts: [],
loading: true,
}
}
// Did Mount.
componentDidMount() {
...
}
// Render.
render() {
let allProducts = this.state.allProducts
let loading = this.state.loading
let categoryURL = this.props.location.state.category
return (
<div>
<Head/>
<ProductsIteration
allProducts={allProducts}
loading={loading}
categoryURL={categoryURL}
/>
{this.props.match.params.id ? (<ProductShow/>) : ''}
<Foot/>
</div>
)
}
}
I have a state 'isLoggedIn' in App Component.
Now, I want to pass this state as props to the child component 'Secret Component'.
<BrowserRouter>
<App>
<Switch>
<Route path='/secret' component={Secret} />
<Route path='/' component={Top} />
</Switch>
</App>
</BrowserRouter>
But, I'm using react-router(ver4.1) like this and can't figure out how to pass the state of App Component as props to its child component.
const childrenWithProps = React.Children.map(this.props.children, (child) => {
console.log(child);
}
);
I know, by doing like this, I can get an access to this.props.children and set additional props to them but since I wrap my components with Router Component, the children of App Component are now Route components, which makes it complicated...
Could anyone please tell me how to do it?
I'm also worried if I'm doing wrong on how to use react-router.
thanks!
index.js(entry point)
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import App from './components/App';
import Secret from './components/Secret';
import Top from './components/Top';
ReactDOM.render(
<BrowserRouter>
<App>
<Switch>
<Route path='/secret' component={Secret} />
<Route path='/' component={Top} />
</Switch>
</App>
</BrowserRouter>
,
document.querySelector('.container')
);
App.js
import React, { Component } from 'react';
import NavigationMenu from './NavigationMenu';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
isLoggedIn: false
};
this.toggleAuthenticationStatus = this.toggleAuthenticationStatus.bind(this);
}
toggleAuthenticationStatus() {
this.setState({
isLoggedIn: !this.state.isLoggedIn
});
}
render() {
//I want to pass this.state.isLoggedIn as props to Secret Component!!!
const childrenWithProps = React.Children.map(this.props.children, (child) => {
console.log(child);
}
);
return (
<div>
<NavigationMenu isLoggedIn={this.state.isLoggedIn} toggleAuthenticationStatus={this.toggleAuthenticationStatus} />
{this.props.children}
</div>
)
}
}
Secret.js
import React, { Component } from 'react';
class Secret extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
if (this.props.isLoggedIn === false) {
this.props.history.push('/');
}
}
componentWillUpdate() {
if (this.props.isLoggedIn === false) {
this.props.history.push('/');
}
}
render() {
return (
<div>
This content is only for our members!
</div>
)
}
}
export default Secret;
In react-router v4 recommended approach is putting nested routes inside the parent component instead of pass those as children (see the basic example of react-router v4). So in your case, I suggest you to simply replace {this.props.children} with Routes with the Switch component and stop passing them as the children of App. Then you can use render method of Route to pass props to the Secret component as usual.
return (
<div>
<NavigationMenu isLoggedIn={this.state.isLoggedIn} toggleAuthenticationStatus={this.toggleAuthenticationStatus} />
<Switch>
<Route path='/secret' render={() => <Secret isLoggedIn={this.state.isLoggedIn}/>)} />
<Route path='/' component={Top} />
</Switch>
</div>
)
I am using react router 4. I have two components 1- ShopLogin 2- Shopper. I am trying to redirect from ShopLogin component to Shopper component after button click.
Everything is working fine. URL is also changing after button click. I am able to see 'Hello' also.
But the problem is i am able to see both component on browser after button click. component is not refreshing. not sure why it is happening. Below are my code.
import React from 'react';
import PropTypes from 'prop-types';
export class ShopLogin extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
SignIn(e) {
e.preventDefault();
this.context.router.history.push('/shopper');
}
render() {
return (
<div>
<button onClick={this.SignIn.bind(this)}>SignIn</button>
</div>
);
}
}
ShopLogin.contextTypes = {
router: PropTypes.object
}
export default ShopLogin;
My Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import ShopLogin from './ShopLogin';
import Shopper from './Shopper';
import { HashRouter,Route } from 'react-router-dom';
ReactDOM.render((
<HashRouter>
<div>
<Route path="/" component={ShopLogin} />
<Route path="/shopperlogin" component={ShopLogin} />
<Route path="/shopper" component={Shopper} />
</div>
</HashRouter>
), document.getElementById('root'))
My Shopper.js
import React, { Component } from 'react';
export class Shopper extends Component {
constructor(props)
{
super(props);
this.state = {
};
}
render()
{
return (
<div>
Hello </div>
);
}
}
export default Shopper;
It will show multiple components since on the route '/shopper'. The Routes checks successfully to the ShopLogin Component with the path '/' and it checks successfully to the Shopper Component with the path '/shopper'.
I would create a parent Component e.g. Main that just presents the child components and define the routes like this
import IndexRoute from react-router
import { HashRouter,Route, IndexRoute } from 'react-router-dom';
resort your routes to
<HashRouter>
<Route path='/' component={Main}>
<Route path='/shopper' component={Shopper} />
<IndexRoute component={ShopLogin} />
</Route>
</HashRouter>
Create your parent component for ShopLogin and Shopper components
class Main extends Component {
render(){
return (
<div>
{this.props.children}
</div>
)
}
Try to reorder your routes and use the exact attribute and wrap all the routes with a Switch.
<HashRouter>
<div>
<Switch>
<Route path="/" component={ShopLogin} />
<Route exact path="/shopper" component={Shopper} />
<Route path="/shopperlogin" component={ShopLogin} />
</Switch>
</div>
</HashRouter>