EDIT
My Initial question included routes with split points, but I've reduced it to the most simple use case of just nesting child routes under each other.
For reference I'm using the popular react-redux-starter-kit and I'm trying to add a simple wrapper component to my routes like so:
export const createRoutes = (store) => ({
path: '/',
component: CoreLayout,
indexRoute: Home,
childRoutes: [{
component: TransitionWrapper,
childRoutes: [
CounterRoute(store)
]
}]
})
but I get the following error and my child routes aren't being rendered:
Warning: Failed prop type: Required prop `children` was not specified in `CoreLayout`.
in CoreLayout (created by RouterContext)
in RouterContext (created by Router)
in Router (created by AppContainer)
in div (created by AppContainer)
in Provider (created by AppContainer)
in AppContainer
So basically if I nest child routes in a child route I get a complaint about missing children.
Here is the full setup:
main.js
const MOUNT_NODE = document.getElementById('root')
let render = () => {
const routes = require('./routes/index').default(store)
ReactDOM.render(
<AppContainer
store={store}
history={history}
routes={routes}
/>,
MOUNT_NODE
)
}
/routes/index.js
import CoreLayout from '../layouts/CoreLayout/CoreLayout'
import Home from './Home'
import NestedChild from './NestedChild'
import TransitionWrapper from './TransitionWrapper'
export const createRoutes = (store) => ({
path: '/',
component: CoreLayout,
indexRoute: Home,
childRoutes: [{
component: TransitionWrapper,
childRoutes: [
NestedChild
]
}]
})
AppContainer.js
class AppContainer extends Component {
static propTypes = {
history: PropTypes.object.isRequired,
routes: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
}
render () {
const { history, routes, store } = this.props
return (
<Provider store={store}>
<div style={{ height: '100%' }}>
<Router history={history} children={routes} />
</div>
</Provider>
)
}
}
export default AppContainer
CoreLayout.js
import React from 'react'
import Header from '../../components/Header'
import classes from './CoreLayout.scss'
import '../../styles/core.scss'
export const CoreLayout = ({ children }) => (
<div className='container text-center'>
<Header />
<div className={classes.mainContainer}>
{children}
</div>
</div>
)
CoreLayout.propTypes = {
children: React.PropTypes.element.isRequired
}
export default CoreLayout
TransitionWrappper.js <--- IS NOT RENDERING
const TransitionWrapper = (props) => (
<div className="im-not-working">
{this.props.children}
</div>
)
export default TransitionWrapper
NestedChild.js <--- IS NOT RENDERING
Have you tried just removing isRequired from the children prop of CoreLayout?
If you are loading your child components dynamically, there will be a period of time where the CoreLayout renders before you've got child components to put in it.
Related
I created a route config for react-router-dom following the official docs right here
But when I tried to implement my code, it only renders my first route from the config.
routes.js
import TellerLoginPage from '../TellerLoginPage';
import TellerMenuPage from '../TellerMenuPage';
export const routeConfig = [
{
path: '/teller/login',
exact: true,
component: TellerLoginPage,
},
{
path: '/teller',
exact: true,
component: TellerMenuPage,
},
];
I created a component to wrap the logic of implementing the props into the Route component from react-router-dom.
RouteItem.js
import React from 'react';
import { Route } from 'react-router-dom';
const RouteItem = ({ route }) => {
console.log(route);
return (
<Route
path={route.path}
exact={route.exact}
render={(props) => <route.component {...props} />}
/>
);
};
export default RouteItem;
App.js
import React from 'react';
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
import RouteItem from '../../components/RouteItem';
import { routeConfig } from './routes';
function App() {
const populateRoute = () => {
const jsx = [];
routeConfig.forEach((r, i) => {
jsx.push(<RouteItem route={r} key={i} />);
});
return jsx;
};
return (
<Router>
// ...
<Switch>
{populateRoute()}
</Switch>
// ...
</Router>
)
}
What happened is that the only routes correctly rendered is the one mentioned first in the routes.js array.
But when I tried to render it directly in the App.js, the routes are correctly rendered.
Direct Render - App.js
import React from 'react';
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
import RouteItem from '../../components/RouteItem';
import { routeConfig } from './routes';
function App() {
return (
<Router>
// ...
<Switch>
{routeConfig.map((r, i) => {
return <Route path={r.path} render={(props) => <r.component {...props} key={i} />} />;
})}
</Switch>
// ...
</Router>
)
}
Is there anything that I missed lol I feel so stupid at this point
As #patrick-evans mentioned in the comment
...you are rendering your own component RouteItem directly in the Switch instead of Route directly. Switch might be internally trying to use something like the children prop to access each route...
At the the official example, they use spread syntax (<RouteWithSubRoutes key={i} {...route} />) instead of pass route as route prop: <RouteItem route={r} key={i} />). Therefore Switch can access to route props in the example, but can't in your code.
You should just fix <RouteItem route={r} key={i} />) to <RouteItem {...r} key={i} />) and it should work. And of course change your own component to const RouteItem = (route) => {...
Currently going through this tutorial on creating a sidebar navigation system with react router https://reacttraining.com/react-router/web/example/sidebar
I am planning to have multiple routes, so that means I'll have to keep importing the routes and add them to the routes array. Is there a smart/right way to load them dynamically?
All my components will be in my /Views folder.
App.js
import React, { Component } from 'react';
import SideBar from './components/SideBar/SideBar';
import MainContent from './components/MainContent/MainContent';
import { BrowserRouter as Router,
} from 'react-router-dom';
// Import all components here
import Button from './components/Views/Button/Button';
import Color from './components/Views/Color/Color';
import Card from './components/Views/Card/Card';
import Filter from './components/Views/Filter/Filter';
const routes = [
{
path: "/",
name: 'home',
exact: true,
main: () => <h2>Home</h2>
},
{
path: "/button",
name: 'Button',
main: () => <Button />
},
{
path: "/color",
name: 'Color',
main: () => <Color />
},
{
path: "/card",
name: 'Card',
main: () => <Card />
},
{
path: "/filter",
name: 'Filter',
main: () => <Filter />
},
];
class App extends Component {
render() {
return (
<Router>
<div className="ds-container">
<SideBar routes={routes} />
<MainContent routes={routes} />
</div>
</Router>
);
}
}
export default App;
Since, you're using create-react-app that uses webpack internally, you could look into require-context. This will help you dynamically import all files in a folder that match a certain regex. (ex: ending with .jsx/.js)
However, I'd advice you against it as:
At no point will you know what routes you're currently catering to.
It may decrease your code readability.
You may have to also export the mapping(path in the Route) of the component along with the component itself.
To avoid all of this, You could simply create a index.js file in your Views component that would require any new route component that you create and return the final array that you have formed in the App.js file.
So essentially, /Views/index.js :
// Import all components here
import Button from './components/Views/Button/Button';
import Color from './components/Views/Color/Color';
import Card from './components/Views/Card/Card';
import Filter from './components/Views/Filter/Filter';
const routes = [
{
path: "/",
name: 'home',
exact: true,
main: () => <h2>Home</h2>
},
{
path: "/button",
name: 'Button',
main: () => <Button />
},
{
path: "/color",
name: 'Color',
main: () => <Color />
},
{
path: "/card",
name: 'Card',
main: () => <Card />
},
{
path: "/filter",
name: 'Filter',
main: () => <Filter />
},
// add new routes here
];
export default routes;
In SideBar.js:
import routes from 'path/to/views/Views';
//rest of your code to render the routes.
This way, you would clean up the code in your App.js and would also be able to effectively separate the concerns of the individual components.
I hope this makes sense :)
There are several ways to choose a main component depending on current location. But all of them would require listing all the possible routes and importing respective components. You can use dynamic imports and React.lazy for easier code splitting.
But you can avoid extra configuration for your sidebar. The sidebar can get configuration from global state and your main components can update that state on mount (componentDidMount or useEffect(() => {...}, [])):
const SideBarContext = React.createContext(() => {});
function useSidebar(name) {
const setSidebarName = useContext(SideBarContext);
useEffect(() => {
setSidebarName(name);
return () => setSidebarName(null);
}, []);
}
function Home() {
useSidebar('home');
return <h2>Home</h2>;
}
function Button() {
useSidebar('Button');
return <button>press me</button>;
}
function App() {
const [name, setName] = useState(null);
return (
<Router>
<div className="ds-container">
<SideBar name={name} />
<SideBarContext.Provider value={setName}>
<Route path="/" exact component={Home}/>
<Route path="/button" component={Button}/>
</SideBarContext.Provider>
</div>
</Router>
);
}
So each component will take care about options for sidebar and you only need to import them and add Routes.
Okay, I've spent three hours driving myself crazy trying to figure this out. I'm sure I'm doing something wrong, but this is my first foray into React and I'm not sure exactly what the problem is.
My main app file looks like this:
import React, { PropTypes } from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory'
import routes from './routes.jsx';
import map from 'lodash/map';
import MainLayout from './components/layouts/main-layout.jsx';
const history = createHistory();
const App = ({store}) => (
<Provider store={store}>
<Router history={history} basename="/admin-panel">
<Route component={MainLayout}>
<Switch>
{map(routes, (route, name) => (
<Route key={name} path={route.path} exact={route.exact} component={route.component} />
))}
</Switch>
</Route>
</Router>
</Provider>
);
App.propTypes = {
store: PropTypes.object.isRequired,
};
export default App;
The route is simply a JSON object that contains route info. Example:
import React from 'react';
import DashboardContainer from './components/containers/dashboard-container.jsx';
import AdminUserContainer from './components/containers/admin-users-container.jsx';
export default {
dashboard: {
order: 1,
path: '/',
exact: true,
name: 'Dashboard',
icon: 'tachometer',
component: DashboardContainer,
},
users: {
order: 2,
path: '/admin-users',
name: 'Admin Users',
icon: 'users',
component: AdminUserContainer,
}
};
(There's irrelevant stuff in the object; that's for the sidebar rendering, which also uses this object to render itself)
dashboard-component.jsx looks like this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Dashboard from '../views/dashboard.jsx';
class DashboardContainer extends Component {
render () {
return (<Dashboard />);
}
}
const mapStateToProps = function (store) {
return store;
};
export default connect(mapStateToProps)(DashboardContainer);
And dashboard.jsx looks like this:
import React, { Component } from 'react';
class Dashboard extends Component {
render () {
return (
<p>This is a test.</p>
);
}
}
export default Dashboard;
But for some reason, no matter what, the returned component is UnknownComponent. Apparently, nothing is matching on /admin-panel, and I'm not sure why. I've tried moving components around, merging the Routing stuff with MainLayout (which just contains the stuff that won't change every request, like the sidebar and the header), even tried the HashRouter instead of the BrowserRouter to see if that would do anything. No dice.
Can someone familiar with React tell me just what it is I'm not doing right here?
I am trying to figure out how the react-router works inside the AppContainer when HMR is enable and stuck upon the following error. Can you please explain what the hack is going on?
Invariant Violation: The root route must render a single element
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
// AppContainer is a necessary wrapper component for HMR
import Routes from './routes/index';
const MOUNT_APP = document.getElementById('root');
const render = () => {
ReactDOM.render(
<AppContainer>
<Routes />
</AppContainer>,
MOUNT_APP
);
};
render();
// Hot Module Replacement API
if (module.hot) {
module.hot.accept('./routes/index', () => {
render()
});
}
The route file is:
import React from 'react';
import { Router, IndexRoute, browserHistory } from 'react-router';
import Home from './Home';
import SubView from './Sub';
const componentRoutes = {
component : Home,
path : '/',
indexRoute : SubView,
childRoutes : [
]
}
const Routes = () => {
return (
<Router history={browserHistory} routes={componentRoutes} />
);
};
export default Routes;
HomeView Component:
import React from 'react';
const HomeView = () => {
<div>
<h4>Welcome</h4>
</div>
}
export default HomeView;
HomeView Route:
import HomeView from './components/SubView';
export default {
component: HomeView
}
P.S: SubView is equal to HomeView.
You need to return one element from the component. Right now your component for HomeView looks like this:
const HomeView = () => {
<div>
<h4>Welcome</h4>
</div>
}
You need to return the markup instead of just put it in the function body like this:
const HomeView = () => {
return (
<div>
<h4>Welcome</h4>
</div>
)
}
Your HomeView component does not return anything. You need to wrap the inner jsx in return ( ... ).
So it's my first time setting something like this up and I'm struggling a little bit.
I used https://github.com/reactjs/react-router/tree/master/examples/huge-apps as my source of learning and am trying to set up a very basic layout for a react application.
What seems to be happening is that react isn't re-rendering after a path change therefore nothing ever gets added to the dom
When I click on the go to home component Link the URL bar changes but no DOM changes occur...
Here is my code [i'm leaving out my directory structure since i don't think it's important for the problem]
index.jsx: Load up the react app and get all routes
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import { Router, browserHistory } from 'react-router';
import Routes from './app/Routes.js';
render(
<Router
history={browserHistory}
routes={Routes}
/>,
document.querySelector('.js-mount-point')
);
Routes.js: Constant to keep all of my routes so that I don't have to manually specify them in index.js
import App from './App.jsx';
import Home from '../routes/Home/Home.js';
const Routes = {
path: '/',
component: App,
childRoutes: [
Home,
],
};
export default Routes;
App.jsx: Parent most component for my app
import React from 'react';
import { Link } from 'react-router';
const App = props => {
console.log(props);
return (
<div>
<h1>Hello World!</h1>
<p><Link to="#/home">Go to Home Component</Link></p>
{props.children}
</div>
);
};
export default App;
Home.js Grab all my route information and getComponent lives here
const HomeRoute = {
path: 'home',
title: 'Home',
getComponent(nextState, cb) {
require.ensure([], (require) => {
cb(null, require('./HomeComponent.jsx').default);
});
},
};
export default HomeRoute;
HomeComponent.jsx my very basic home component
import React from 'react';
const HomeComponent = () => (
<div>
<h2>Welcome Home</h2>
</div>
);
export default HomeComponent;
Edit1: Made App.jsx pure function
Edit2: Fixed Routes.js
Edit3: Fixed Home.js
Edit4: Final fix,
const HomeComponent =
changed to
const HomeComponent = () => (
You App should be a component not a function returning an object. Right now you are mixing two approaches.
Either a function (stateless component)
import React from 'react';
import { Link } from 'react-router';
const App = props => {
console.log(props.children);
return (
<div>
<h1>Hello World!</h1>
<p><Link to="/home">Go to Home Component</Link></p>
{props.children}
</div>
);
};
export default App;
Or a statefull component that has render method
import React, { Component } from 'react';
import { Link } from 'react-router';
class App extends Component {
render() {
console.log(this.props.children);
return (
<div>
<h1>Hello World!</h1>
<p><Link to="/home">Go to Home Component</Link></p>
{this.props.children}
</div>
);
}
};
export default App;
Same with HomeComponent
const HomeComponent = () => (
<div>
<h2>Welcome Home</h2>
</div>
);
And you need to fix route config as well
const HomeRoute = {
path: 'home', //no # needed
title: 'Home',
getComponent(nextState, cb) {
require.ensure([], (require) => {
cb(null, require('./HomeComponent.jsx').default);
});
},
};
Ohh, and I think you need.
const Routes = {
path: '/',
component: App,
childRoutes: [
Home
],
};