Couldn't pass root component props to children components - reactjs

I'm fairly beginner in react/redux, and I'm creating a simple project using React, Redux and React Router v4. I use Redux to handle the state and it sucessfully passes states to root component Wrapper, but it seems it doesn't pass it to Home or other components.
When I console log this.props.Gallery in Wrapper, the data displays but when I do it on Gallery, it shows 'undefined'. I've passed children in Wrapper using React.cloneElement but it didn't work. Is there an explanation to this and some workaround?
My app project structure is
--wrapper
-home
-gallery
-about
-links
Here are the components
For Routing
App.js
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import Wrapper from './components/Wrapper';
...
//import store
import {Provider} from 'react-redux'
import store , {history} from './store'
//router
const mainRouter=(
<Provider store={store} history={history}>
<Wrapper>
<BrowserRouter>
<Switch>
<Route exact path='/' component={ Home } />
<Route path='/portfolio' component={ Gallery } />
<Route path='/about' component={ About } />
<Route path='/links' component={ Links } />
</Switch>
</BrowserRouter>
</Wrapper>
</Provider>
)
ReactDOM.render(mainRouter, document.getElementById('root'))
Wrapper
import React from 'react'
import {bindActionCreators} from 'redux'
import {connect} from 'react-redux'
import * as actionCreators from '../actions/actionCreators'
function mapStateToProps(state){
return{
gallery:state.gallery
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators(actionCreators,dispatch)
}
class Wrapper extends React.Component{
render(){
return(
<div>
{React.cloneElement(this.props.children, this.props)}
</div>
)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Wrapper)
Home.js
import React from 'react'
import { NavLink } from 'react-router-dom'
//import component
...
class Home extends React.Component {
render(){
console.log(this.props.gallery)
return(
<div>
<p>this is home page</p>
<Header/>
<Nav/>
<Footer/>
</div>
)
}
}
export default Home;
Update 9/19
I've managed to created nested components in react router v4, but still unable to pass props to its direct children. It throws an error of children being undefined
And i've also updated my component structure to
--Home ~ this is more like a welcome containing app's menu
--Wrapper
-gallery
-about
-links
Here is what I did: I've moved Wrapper to be wrapper of gallery, about and links
App.js
<Provider store={store} history={history}>
<BrowserRouter>
<div>
<Route exact path="/" component={Home}/>
<Route path="/h" component={Wrapper}/>
</div>
</BrowserRouter>
</Provider>
Wrapper.js
function mapStateToProps(state){
return{
gallery:state.gallery
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators(actionCreators,dispatch)
}
class HomePage extends React.Component {
render() {
return(
<div>
<Route path={`${this.props.match.path}/portfolio`} component={ Gallery } />
<Route path={`${this.props.match.path}/about`} component={ About } />
<Route path={`${this.props.match.path}/links`} component={ Links } />
</div>
)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Wrapper)

You are correctly passing the props from Wrapper down to it's children. However, Home and Gallery are not it's children. They are great great grand children. The only child of Wrapper is the element(s) directly within it. In this case, that is BrowserRouter.
There are a great number of ways that you could make these props available to Home and Gallery. But passing directly from Wrapper is not one of them.
The simplest way would be to make a GalleryContainer class. This would be identical to the Wrapper class you already created (include it's connections to redux), except that it would explicitly render Gallery, as such;
render() {
return <Gallery {...this.props}/>;
}
Then use this new GalleryContainer as the component for the /portfolio route.

I finally managed to pass the props to child components.
I created two containers for my project, one as main container and other as parent to sub components Gallery, About and Links.
Here is structure in App.js
const mainRouter=(
<Wrapper>
<Provider store={store} history={history}>
<BrowserRouter>
<div>
<Route exact path="/" component={Home}/>
<Route path="/h" component={Container}/>
</div>
</BrowserRouter>
</Provider>
</Wrapper>
)
Then in Container, I connected it to redux, and created route to its sub children components and used render to render the components and pass gallery props to Gallery
<Route path={`${this.props.match.path}/portfolio`} render={()=><Gallery {...this.props.gallery}/>}/>
Here is the Container
import React from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as actionCreators from '../actions/actionCreators'
import { Route } from 'react-router-dom'
//import components
...
function mapStateToProps(state) {
return {
gallery: state.gallery
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(actionCreators, dispatch)
}
class Container extends React.Component {
render() {
return (
<div>
<ResponsiveNav/>
<main>
<Route path={`${this.props.match.path}/portfolio`}
render={ ()=><Gallery{...this.props}/> } />
<Route path={`${this.props.match.path}/links`}
render={ ()=><Link{...this.props}/> } />
<Route path={`${this.props.match.path}/about`}
render={ ()=><About{...this.props}/> } />
</main>
<Footer/>
</div>
)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Container)

Related

this.props.children dosent render

Im new to react and Im trying to understand context.
So I tried to create a provider and a consumer for user.
Ftm Im just trying to show the value but im going to pass it into the router and other components later.
This is the provider
const UserContext = React.createContext();
// Create an exportable consumer that can be injected into components
export const UserConsumer = UserContext.Consumer
// Create the provider using a traditional React.Component class
class UserProvider extends React.Component {
render() {
return (
// value prop is where we define what values
// that are accessible to consumer components
<UserContext.Provider value={{
username: 'Crunchy Crunch',
dateJoined: '9/1/18',
membershipLevel: 'Silver'
}}>
{this.props.children}
</UserContext.Provider>
)
}
}
And this is the app:
class App extends React.Component {
render() {
return (
<UserProvider>
<Fragment>
<Router>
<Nav />
<UserConsumer>
{state => (
<p>Username: {state.username}</p>
)}
</UserConsumer>
<Switch>
<ProtectedRoute exact path="/profile" component={Profile} />
<Route exact path="/" component={Index} />
</Switch>
</Router>
</Fragment>
</UserProvider>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
I dont get any errors in the terminal but I get this message in the browser:
Functions are not valid as a React child. This may happen if you
return a Component instead of from render. Or maybe you
meant to call this function rather than return it.
What is the error and is there something fundamental Im not doing correctly.
Br
I have taken you code snippets and run it in codesandbox and it seems ok to me, without seeing the rest of you files its hard to say what the issue. Maybe it to do with how you are importing and using react router. Here is my working snippets
// App
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";
import { Switch, Route } from "react-router";
import Page1 from "./Page1";
import Nav from "./Nav";
import UserProvider, { UserConsumer } from "./Provider";
import "./styles.css";
function App() {
return (
<UserProvider>
<Router>
<Nav />
<UserConsumer>
{state => <p>Username: {state.username}</p>}
</UserConsumer>
<Switch>
<Route exact path="/" component={Page1} />
</Switch>
</Router>
</UserProvider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
// Provider
import React from "react";
const UserContext = React.createContext();
// Create an exportable consumer that can be injected into components
export const UserConsumer = UserContext.Consumer;
// Create the provider using a traditional React.Component class
export default class UserProvider extends React.Component {
render() {
return (
// value prop is where we define what values
// that are accessible to consumer components
<UserContext.Provider
value={{
username: "Crunchy Crunch",
dateJoined: "9/1/18",
membershipLevel: "Silver"
}}
>
{this.props.children}
</UserContext.Provider>
);
}
}
Here is a link to the the working codesandbox
https://codesandbox.io/s/sweet-smoke-y3t9j

Nesting routes along with redux integration not working?

I'am nesting routes in my project. I have App.js in which I have defined the routes and inside the component I have more routes which I want them to be nested. The only problem is that my nested route is in the component which is connected to redux. The nested routing is not working properly.
I have already tried it from the official documentation but it does not work.
https://reacttraining.com/react-router/core/guides/philosophy
App.js
import { connect } from "react-redux";
import { withRouter, Route } from "react-router-dom";
function HowItWorks() {
return (
<div>
<h2 style={{ margin: 20 }}>How It Works</h2>
</div>
);
}
function AboutUs() {
return (
<div>
<h2 style={{ margin: 20 }}>About Us</h2>
</div>
);
}
class App extends React.Component {
render() {
return (
<div>
<Route path="/" exact component={HowItWorks} />
<Route path="/howitworks" exact component={HowItWorks} />
<Route path="/aboutus" component={AboutUs} />
<Route path="/admin" component={AdminContainer} />
</div>
);
}
}
Below is my Redux Container file which gets called based on the route specified in App.js. Also my App.js file may get connected to redux in the future by the connect() method.
AdminContainer.js
import { connect } from "react-redux";
import MainDesktopComponent from "../components/Admin/MainDesktopComponent";
const mapStateToProps = state => ({});
const mapDispatchToProps = dispatch => {
return {};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MainDesktopComponent);
MainDesktopComponent.js
I have tried this i.e giving the nested route inside Switch and many different ways but it is not working. Also note that I also want to pass props to the Dashboard component which will come from the above redux container component through mapstatetoprops.
import React from "react";
import Dashboard from "./Dashboard";
import { Route } from "react-router-dom";
import { Switch } from "react-router";
function MainDesktopComponent(props) {
return (
<div>
<Switch>
<Route
exact
path="/admin/dashboard"
render={props => {
<Dashboard/>;
}}
/>
</Switch>
</div>
);
}
export default MainDesktopComponent;
I'm not sure but what about try this?
<Switch>
<Route
exact
path="/admin/dashboard"
render={cProps => <Dashboard {...cProps} {...props} />}
/>
</Switch>
return Route render component.

React Router will not render Route component unless page is refreshed

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>

URL change but page does not refresh

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>

React pass dynamic state to all routes in router

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.

Resources