React this.props.children is undefined - reactjs

I have the following router config:
ReactDOM.render((
<Router history={History.createHistory()}>
<Route path="/" component={Page}>
<IndexRoute component={OverviewDashboard}/>
<Route path="/:env" component={Env}>
<IndexRoute component={EnvOverview}/>
</Route>
</Route>
</Router>
), document.getElementById('page'));
and the following component definitions:
const Page = React.createClass({
render: function () {
return (
<main>
<Header />
{this.props.children}
</main>
);
}
});
const OverviewDashboard = React.createClass({
render: function () {
return (
<section>
<Env name="Env1" />
<Env name="Env2" />
</section>
);
}
});
const Env = React.createClass({
render: function() {
return (
<section className="env">
<header>{this.props.params && this.props.params.env || this.props.name}</header>
{this.props.children}
</section>
);
}
});
const EnvOverview = React.createClass({
render: function() {
return (
<div>
<Jobs env={this.props.params.env}/>
<Runtime env={this.props.params.env}/>
</div>
)
}
});
I've ommitted the definitions of <Header>, <Jobs> and <Runtime> because they are not relevant.
When i go to / link {this.props.children} within <Env> component is undefined and thus not rendered.
However when I go to "/Env1" {this.props.children} is set to <EnvOverview> correctly and displayed.
I am new to React and probably I am using the routing component incorrectly. Could someone explain how do I render <EnvOverivew> also when accessing / link ?

When you go to /Env, react-router renders your /:env route, whose component is Env. Inside this route, you have an IndexRoute. This IndexRoute renders the EnvOverview component as its parent route children. Thus, you get {this.props.children} on this case.
When you enter the / route, it triggers your IndexRoute inside App, whose component is OverviewDashboard. It doesn't enter the /:env route because you didn't provide a parameter to match :env.
Inside your OverviewDashboard, you are manually rendering two Envs, without providing their children (check this). Thus, inside these two Envs, {this.props.children} equals undefined.
I don't know what your requirements are, but you could change the OverviewDashboard render method to this:
render: function () {
return (
<section>
<Env name="Env1"><EnvOverview name="Env1" /></Env>
<Env name="Env2"><EnvOverview name="Env2" /></Env>
</section>
);
}
And inside EndOverview render method, change env={this.props.params.env} to env={this.props.params.env || this.props.name}.
I don't know if it's the best solution to your case, but I hope why {this.props.children} was undefined is clarified ;)

Related

React - Call function from non-parent component with state from separate component

I'm attempting to call a function from an AppBar with the state from a child component, like so
// App.js
<BrowserRouter>
<Nav />
<Routes>
<Route exact path={"/"} element={<MyComponent/>}/>
<Routes>
</BrowserRouter>
//Nav.js
function Nav() {
return (
<div>
<h1>Hello World</h1>
<button onClick={logChildState}>Get State</button>
</div>
)
}
// MyComponent.js
function MyComponent() {
const [someState, setSomeState] = useState({
Some state values....
})
return (
<div>
<input />
...more components...
</div>
)
}
logChildState() == "Some state values...."
The goal is to have the AppBar have a button with a function call that captures the state of MyComponent. As this is a simplified example, I will just say that the state should exist in the child, and it's not possible to hoist the state to App.js - because of this, I don't see a way to accomlish what I'm looking for easily, I've looked at possibly achieving this using context or an observable but it would be quite messy.
I'm wondering what the best way to tackle this kind of issue would be, or if my best choice would just be to have the "button" in Nav.js in the MyComponent.js.
Thanks
You can add the function as a prop like this:
//Nav.js
function Nav({logChildState}) {
return (
<div>
<h1>Hello World</h1>
<button onClick={() => logChildState('send this message')}>Get State</button>
</div>
)
}
And in your app Component you can simply take it as a prop like this:
<Nav logChildState = {logChildState}/>
and if you want to print the message comming from Nav component simply do this in App.js
const logChildState = (message) => {
console.log(message);
}
Hope that helps!

Adding a Link as a child of a Router with ReactDOM.render yields "You should not use <Link> outside a <Router>"

I am looking for a way to use ReactDOM.render to create a Link within a react router. The setup more or less looks like this:
const router = (
<div>
<Router>
<Route path="/map" component={Map}/>
</Router>
</div>
);
The relevant parts of Map.jsx look like this:
const MapPopup = () => {
return (
<Link to={`/map/add`} />
)
}
class Map extends React.Component {
componentDidMount() {
this.map = L.map('map')
//...stuff...
this.map.on('contextmenu', event => {
popup
.setLatLng(event.latlng)
.addTo(this.map)
.setContent(
ReactDOM.render(
MapPopup(),
document.querySelector('.leaflet-popup-content')
)[0]
)
.openOn(this.map)
})
}
render() {
return (
<React.Fragment>
<div id="map" />
</React.Fragment>
)
}
}
I am basically trying to add a Link to the map popup provided by leaflet (I can't use react-leaflet for this project). If I however return the MapPopup directly in the render function it works (obviously not in the popup but the Link does work this way).
<React.Fragment>
<div id="map" />
<MapPopup />
</React.Fragment>
Does anyone have an idea how I can tackle this rather unusual problem?
I am using "react-router-dom": "4.3.1".
This is the expected error since <Link> component expects ancestor component to be of router type (<BrowserRouter>, <MemoryRouter>, <Router> ... ), refer this thread for a more details.
For your scenario to circumvent this limitation ReactDOM.createPortal could be utilized instead of ReactDOM.render:
<Route
path="/popup"
render={() => (
<Popup>
<div>
Some content goes here
<Link to="/map"> Back to map</Link>
</div>
</Popup>
)}
/>
where
class Popup extends React.Component {
render() {
return ReactDOM.createPortal(
this.props.children,
document.querySelector("#link-render-div")
);
}
}
and
Here is a demo for your reference

How can i pass a component to another component via props

I have this scenario where my web application would have a header with 3 options. Depending on the options selected, the headers will be re-rendered with new options. Since I am new to React, my immediate idea of the code structure would be to have a empty main Header.js file which would render another component which is unique to the page's option. However, my googling didn't return any searches that would help me understand how to pass components to another via react-router v4.
An example:
Header: Steak | Pasta | Burgers
If the user selects Steak, the same header would now display the following:
Header: Black-Pepper | Mushroom | Chilli
The contents of the header is supposed to change according to what the user selected previously
Thank you and I hope I do not get mark down because I really have no idea how else to ask this question.
I prepare you a basic idea for you, with react-router-dom:
const {Router, Route, IndexRoute, Link} = ReactRouter;
// A main React component using this.props.children will pull in all the children Routes in the router function at the bottom.
const App = React.createClass({
render: function() {
return(
<div>
{this.props.children}
</div>
);
}
});
const Home = React.createClass({
render: function() {
return(
<div>
<ul>
<li><Link to="link-steak">Steak</Link></li>
<li><Link to="link-pasta">Pasta</Link></li>
<li><Link to="link-burgers">Burgers</Link></li>
</ul>
</div>
);
}
});
const LinkOne = React.createClass({
render: function() {
return(
<div>
<ul><li>steak 1</li><li>steak 2</li><li>steak 3</li></ul>
<Link to="/">back</Link>
</div>
);
}
});
const LinkTwo = React.createClass({
render: function() {
return(
<div>
<ul><li>pasta 1</li><li>pasta 2</li><li>pasta 3</li></ul>
<Link to="/">back</Link>
</div>
);
}
});
const LinkThree = React.createClass({
render: function() {
return(
<div>
<ul><li>burger 1</li><li>burger 2</li><li>burger 3</li></ul>
<Link to="/">back</Link>
</div>
);
}
});
ReactDOM.render((
<Router>
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path="link-steak" component={LinkOne} />
<Route path="link-pasta" component={LinkTwo} />
<Route path="link-burgers" component={LinkThree} />
</Route>
</Router>
), document.getElementById('app'));
https://codepen.io/ene_salinas/pen/KGbEoW?editors=0010
There are a few ways to go about this but without knowing your project in depth here's a general approach you could use:
(You can create functional components and use them or just write all the JSX in the Header component.)
Header component extends React.Component {
state= {
selected: "burger"
}
// Create a method for each: "burger", "steak" etc...
this.setBurgerMenu = () => {
this.setState({selected: "burger"}, () => {})
}
render() {
return (
<div>
<button onClick={this.setBurgerMenu}>Burger</button>
<button onClick={this.setSteakMenu}>Steak</buttton>
<button onClick={this.setChiliMenu}>Chili</button>
<div>
{this.state.selected === "burger" && <BurgerMenu />}
{this.state.selected === "steak" && <SteakMenu />}
{this.state.selected === "chili" && <ChiliMenu />}
<div>
</div>
)
}
}
If you're making a nav bar and these menu items simply point to a link then the easiest solution would be to use a library with menus and sub menus.

React Router renders two components instead of one [duplicate]

This question already has answers here:
React Router v4 renders multiple routes
(3 answers)
Closed 5 years ago.
I am trying to render two different components with the path /acts and /acts/lite. The problem is that when I go to /acts/lite I get both components rendered instead of just the one that correspond to this path...
For example, when I'm navigating to /acts I get this:
And when I'm navigating to /acts/lite the text Test Acts is still there...
This is my Router:
<Router>
<div>
<App>
<Route exact path="/login" component={LoginPage}/>
<div className="flexible-width container">
<Route path="/acts" component={requireAuth(ActsPage)} />
<Route path="/acts/lite/" component={requireAuth(ActsLitePage)} />
</div>
</App>
</div>
</Router>
ActsPage Component:
class ActsPage extends React.Component {
render() {
return (
<div>
Test Acts
</div>
);
}
}
export default ActsPage;
ActsLitePage Component:
class ActsLitePage extends React.Component {
render() {
return (
<div>
Testing Acts Lite
</div>
);
}
}
export default ActsLitePage;
And this is the Component that shows the sidebar with those two links:
var Sidebar = React.createClass({
render() {
var sidebarClass = this.props.isOpen ? 'sidebar open' : 'sidebar';
return (
<div className={sidebarClass}>
<div><Link to="/acts/">Acts</Link></div>
<div><Link to="/acts/lite/">Acts Lite</Link></div>
</div>
);
}
});
export default Sidebar;
I really don't know what I'm doing wrong or why it behaves like this..
You missed "exact" in your route:
<Link exact to="/acts/">Acts</Link>

React with React-Router: RouteHandler component is completely ignored in the output

I'm working my way through the react-router guides (http://rackt.github.io/react-router/), and have a nice little router set up. But the Route node in the routes variable completely ignores the {Application} handler, and just straight-up renders the Dashboard onto the page, with no control over where it goes. Any ideas?
Here is the code I'm using as my test:
var React = require("react"),
Router = require("react-router"),
Route = Router.Route,
DefaultRoute = Router.DefaultRoute,
RouteHandler = Router.RouteHandler, // I can comment this out and nothing breaks
App = {
Dashboard: require("./js/components/Dashboard.jsx")
},
routes = (
<Route path="/" handler={Application}>
<DefaultRoute handler={App.Dashboard} />
</Route>
),
// This React component is ignored, even though it's set as the handler in the routes object
Application = React.createClass({
render: function() {
return (
<div>
<h1>THIS TITLE IS NEVER ACTUALLY RENDERED</h1>
{/* The RouteHandler here is just ignored. I can comment it out, and rendering is identical! */}
<RouteHandler />
</div>
)
}
});
Router.run(routes, Router.HistoryLocation, function (RouteHandler) {
React.render(<RouteHandler/>, document.getElementById("app));
});

Resources