Split Transclusion React - react-router

What I'm wanting to do is have children components influence children components of its parent. Tangibly: I want my content components to be able to inject components into my header and sidebar elements that are pre-defined.
Router:
<Router history={browserHistory}>
<Route path="/" component={AppRoot}>
<Route path="/home" component={Home}/>
<Route path="/list" component={List}/>
<Route path="/profile/:uid" component={Profile}/>
</Route>
</Router>
App Root:
class AppRoot extends Component {
constructor() {
super();
this.state = {
header_elements: <div>HELLO???</div>
};
console.log(this);
}
render() {
return (
<div>
<AppBar className="app_bar" title="Soulhart" showMenuIconButton={false}>
<div id="nested_content">
{this.state.header_elements}
</div>
</AppBar>
<div>
{this.props.children}
</div>
</div>
);
}
}
Home:
class Home extends Component {
render() {
return (<strong>Home</strong>);
}
}
My navigation and header are defined inside AppRoot, but I'm not sure how to have Home set the AppRoot.header_elements value. Is there a simpler way of doing this, or is what I want impossible?

The way you do this is to have a property of type func on your child component -
Home.propTypes = {
updateRoot : React.PropTypes.func
};
And then call that from home. Ofcouse, you would need to wire it up in the router
<Route path="home" component={() => <Home updateRoot={this.handleUpdateRoot}/>}/>

Related

Nesting React routes to login-protected pages [duplicate]

This question already has answers here:
How to create a protected route with react-router-dom?
(5 answers)
Closed 8 months ago.
I am using react-router-dom#6.3.0
I have created a React app where certain Private pages are accessible only users who have logged in.
You can find a demo here, and a GitHub repo here.
A simplified version of this is shown below.
I have wrapped every Private page in its own RequireLogin component, and this works:
<Route
path="/private1"
element={
<RequireLogin redirectTo="/">
<Private
text="Private Page #1"
/>
</RequireLogin >
}
/>
The RequireLogin component redirects to a page with the Login component if the user is not logged in, and renders the requested component only to a logged in user.
My question is this:
Is it there a way to wrap all the Private routes inside one RequireLogin component, or do I have to wrap each one separately?
import React, { createContext, useContext, useState } from 'react'
import ReactDOM from 'react-dom';
import {
BrowserRouter as Router,
Routes,
Route,
Navigate,
NavLink
} from "react-router-dom";
const UserContext = createContext()
const UserProvider = ({children}) => {
const [ loggedIn, logIn ] = useState("")
return (
<UserContext.Provider
value={{
loggedIn,
logIn
}}
>
{children}
</UserContext.Provider>
)
}
function App() {
return (
<Router>
<UserProvider>
<Routes>
<Route
path="/"
element={<NavLink to="/login">Log In</NavLink>}
/>
<Route
path="/login"
element={<Login />}
/>
<Route
path="/private1"
element={
<RequireLogin redirectTo="/login">
<Private
text="Private Page #1"
/>
</RequireLogin >
}
/>
<Route
path="/private2"
element={
<RequireLogin redirectTo="/login">
<Private
text="Private Page #2"
/>
</RequireLogin >
}
/>
</Routes>
</UserProvider>
</Router>
);
}
function Menu({hideLogOut}) {
const { loggedIn } = useContext(UserContext)
if (loggedIn) {
if (!hideLogOut) {
return <ul>
<li><NavLink to="/login">Log Out</NavLink></li>
<li><NavLink to="/private1">Private #1</NavLink></li>
<li><NavLink to="/private2">Private #2</NavLink></li>
</ul>
} else {
return <ul>
<li><NavLink to="/private1">Private #1</NavLink></li>
<li><NavLink to="/private2">Private #2</NavLink></li>
</ul>
}
} else {
return <p>Not Logged In</p>
}
}
function RequireLogin ({ children, redirectTo }) {
const { loggedIn } = useContext(UserContext);
return loggedIn
? children
: <Navigate to={redirectTo} />;
}
function Private({text}) {
return (
<div>
<Menu />
<h1>{text}</h1>
</div>
)
}
function Login() {
const { loggedIn, logIn } = useContext(UserContext)
const toggleLogged = () => {
logIn(!loggedIn)
}
return (<div>
<Menu
hideLogOut={true}
/>
<label htmlFor="loggedIn">
<input
type="checkbox"
name="loggedIn"
id="loggedIn"
checked={loggedIn}
onChange={toggleLogged}
/>
Pretend that we are logged in
</label>
</div>)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
I use a second router for the private routes, wrapped with a single <RequireLogin>. Example:
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegistrationPage />} />
<Route path="*" element={
<RequireLogin>
<Routes>
<Route path="/" element={<FeedPage />} />
<Route path="/explore" element={<ExplorePage />} />
<Route path="/user/:username" element={<UserPage />} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
</RequireLogin>
} />
</Routes>

react router pass props to children

I've seen the question on stackoverflow and other resources, but still I can't see a clear answer. Why on earth I can't pass props down to children in react router? React stands on that pillar of passing props in one direction. What am I missing? Here's my code:
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom'
import Home from './Home.js'
import Favourites from './Favourites.js'
class App extends React.Component {
render() {
const cards = [
{
id: 1,
name: 'Buzz',
selected: false
},
{
id: 2,
name: 'Buzzina',
selected: true
}
]
return(
<Router>
<div className='app'>
<nav>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/favourites'>Favourites</Link></li>
</ul>
</nav>
<Route exact path='/' component={ Home } />
<Route path='/favourites' component={ Favourites } />
</div>
</Router>
)
}
}
export default App
How can I pass props to my Home and Favorites components if I don't want to use black magic and shamanic dance?
You can try this:
const MyHome = (props) => {
return (
<Home {...props} />
);
}
Then you have to use renderinstead of component
<Route exact path="/products" render={MyHome} />

<Nav> is not re-rendering on location change even though wrapped in <Route>

I am using react-router-dom. I have a child within a <Route> that is not re-rendering even though the location is changed due to click on <NavLink> in the <Nav>. The <Header> is responsible for rendering the <Route> which renders the <Nav>:
The <Header> renders this:
class Header extends PureComponent<void, void, void> {
render() {
return (
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Catchem</h2>
<Route component={Nav} />
</div>
)
}
}
We see that line <Route component={Nav} />, I am expecting this to re-render every time the path changes. However it is not. Can you please help me understand why it is not re-rendering when path changes? Clicking the <NavLink>s in the <Nav> is causing a path change.
Here is my <Nav>:
class Nav extends PureComponent<void, Props, void> {
render() {
const { location } = this.props;
console.log('nav props:', this.props);
return (
<div className="nav">
{ PAGES.map( ({path, label}) => (
<NavLink exact className="topnav-link" activeClassName="topnav-link-selected" key={path} to={path} location={location} exact>
{label}
</NavLink>
)) }
</div>
)
}
}
This is my <App>:
const PAGES = [
{ path:'/', label:'Asset Manager', Body:AssetManager },
{ path:'/tags', label:'Tag Manager', Body:TagManager },
{ path:'/tag-groups', label:'Tag Group Manager', Body:TagGroupManager },
]
class App extends Component {
render() {
return (
<Provider store={store}>
<BrowserRouter>
<div className="App">
<Header />
<p className="App-intro">
Gotta <code>catch'em</code> all!!
</p>
<Switch>
{ PAGES.map( ({ Body, path }) => <Route path={path} key={path} exact component={Body} /> ) }
<Route component={Body404} />
</Switch>
</div>
</BrowserRouter>
</Provider>
)
}
}

Migrating from React Router v2 to v4

So, I'm currently using react-router v2 as follows:
import { IndexRoute, Router, Route, Redirect } from 'react-router';
import App from './components/App';
....
render () {
return (
<ApolloProvider store={store} client={client}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={PhotoGrid} />
<Route path="/view/:postId" component={Single}></Route>
<Route path="/login" component={LoginUser}></Route>
</Route>
</Router>
</ApolloProvider>
)
}
}
export default MainApp;
App.js
....
import Main from './Main';
const allPostsCommentsQuery = graphql(All_Posts_Comments_Query, {
options: {
cachePolicy: 'offline-critical',
fetchPolicy: 'cache-first',
},
});
function mapStateToProps(state) {
return {
auth: state.auth
};
}
export const mapDispatchToProps = (dispatch) => {
return bindActionCreators(actionCreators, dispatch);
}
export default compose(
allPostsCommentsQuery,
connect(mapStateToProps, mapDispatchToProps)
)(Main);
Main.js
class Main extends React.Component {
constructor (props) {
super(props);
}
componentWillMount () {
if (!this.props.auth.token){
this.context.router.push('/login');
}
}
render () {
return (
<div>
<h1>
<Link to="/">Flamingo City</Link>
</h1>
{ React.cloneElement(this.props.children, this.props) }
</div>
);
}
}
Main.contextTypes = {
router: function() { React.PropTypes.func.isRequired }
};
export default Main;
How do I convert my current v2 router to v4? What I am not clear on, is the parent nested element:
<Route path="/" component={App}>
In all the v2 -> v4 conversion examples I have seen thus far, none clearly explain what happens to the child elements. Am I expected to place the child elements within the App.js component itself, and if so, in the version of my App.js, how would that work as the first sign of any navigation actually occurs with Main.js?
Really useful post on github where you can see all the important parts of migrating to v4.
https://gist.github.com/kennetpostigo/7eb7f30162253f995cd4371d85c1e196
Also explaining how to go about child routes. Basically, you are supposed to place a Match inside App.js so this parent component will become responsible for its own part of child routes, an so on with every parent component.
Haven't tried this, let me know how it goes!

Component in nested route renders itself instead in this.props.children

I am trying to display a form, nested inside a parent view using the following route configuration:
const routes = (
<Router history={history}>
<Route path='/' component={App}>
<IndexRoute component={Landing}/>
<Route path='applications' component={Applications}>
<Route path='/new' component={ApplicationForm}/>
</Route>
</Route>
</Router>
);
When visiting /applications, I render my Applications component
class Applications extends Component {
renderContent () {
return (
<div className="dashboard">
{this.props.children}
</div>
);
}
render () {
return (
<DashboardLayout
content={this.renderContent()}
{...this.props}/>
);
};
}
But for some reason, the Applications component also gets rendered into this.props.children, but without the props of the initial render