React Router v4 dynamic layouts without rerenders - reactjs

I'm trying to make a site that is fully protected behind a login page. Once the user has logged in, they are redirected to the main page, which has a completely different layout.
MainLayout (includes header, sidebar etc.)
LoginLayout (centers everything on the page, but doesn't include the above elements)
FutureLayout (I want to be able to use different layouts for different pages later)
At the moment, the biggest problem I'm facing is that every time I change my route, everything rerenders. I sort of understand why this is happening, but I've been unable to find a way around it.
Here is some of my current progress
First thought
https://codesandbox.io/s/6l10v4o7jn
const HomePage = () => (
<MainLayout>
<h1>HOME PAGE</h1>
</MainLayout>
);
This obviously doesn't work because every time you move to another page, the Pagererenders the Layout. You can see this in action by toggling the green button on either HomePage or AboutPage then switching routes
Second Thought
https://codesandbox.io/s/moj437no58
<Route render={() => (
<MainLayout>
<Route exact path="/" component={HomePage} />
<Route path="/about" component={AboutPage} />
</MainLayout>
)} />
<Route render={() => (
<LoginLayout>
<Route path="/login" component={LoginPage} />
</LoginLayout>
)} />
This fixes the state problem, but of course it renders both layouts because there's noting telling it not to, but the page is still determined by the router, as expected.
In a real application, those routes would be extracted to their own component like <AuthRoutes> and <PublicRoutes>
Why some solutions might not work
I'm also planning on using connectedRouterRedirect from redux-auth-wrapper to deal with protecting my routes, which can be a problem when using some of the suggestions I've found.
This I believe is because how the wrapper works as a HOC in conjuntion with Route's component prop
i.e <Route component={mustBeAuth(Page)} />
Conclusion
I've been stuck with this for a while, and I feel like I'm getting confused with various techniques and suggestions. Hopefully someone here can have a fresh take on it and perhaps help me figure out a solution.

Related

What is the correct way to setup an independent Route that is not a part of App.js in React Router Dom?

I'm trying to create an independent Route (not sure if that's the correct term) at BulletinBoard.js where I can use Link to go to Create Bulletin component.
I'm also trying to create another independent Route at BulletinList.js where I can navigate to Edit Bulletin with their respective IDs.
Before this, I tried using useRouteMatch with path and url, but apparently that wasn't the correct way to do it, now I'm told to use useLocation, it does adds the /createbulletin and /editbulletin/id paths behind the current URL, but it doesn't navigate to the component itself.
I've been cracking my head over this for the past 2 days and I still haven't figured out the correct way to do this.
Here is the codesandbox that I've created for reference.
The reason your code didnt navigate to a different component after the url changed is because you didnt use the exact attribute when declaring the route. So its matching /bulletinboard/anything and then it always renders de BulletinBoard component.
You could define all routes at the App.js file like
<Switch>
<Route path="/" component={Home} exact />
<Route path="/bulletinboard" component={BulletinBoard} exact />
<Route path="/bulletinboard/edit/:id" component={EditBulletinBoard} exact />
<Route path="/infohub" component={InfoHub} exact />
<Route component={NotFound} />
</Switch>
Also, check out the useHistory hook
So at the BulletinBoard.js when the user clicks the link
onClick={() => history.push(`/bulletinboard/edit/${id}`)}
Note that the edit route renders a different component that your codesandbox didn't have yet

Hide React Elements Based on Session Storage or Page

I have React application which has a structure similar to the following.
class App extends Component {
render() {
return (
<div className="App">
<NavBar />
<Router>
<Switch>
<Route path="/login" component={LoginPage} />
<Route path="/" exact component={DashboardPage} />
<Route path="/admin" exact component={AdminPage} />
// many other routes
<Route component={NotFound} />
</Switch>
</Router>
</div>
);
}
}
I do not want the login page to display the <NavBar /> element. I tried using the sessionStorage to get the userId and only display the navigation if the value is set. When I do this and go to the login page and the nav bar is not there. But when I log in, it's still not there. If I refresh however, it will appear.
I know one way to solve this is to make some sort of wrapper around the pages that do want the navigation, but I'd rather not have all of that code duplication, etc.
I feel this must be a common want, and I'm missing something dumb. Any help would be appreciated. I'm new to React so I don't follow everything that's going on here. Thanks.
I think your way of conditionally showing the NavBar is the right way. The question is how to trigger a state change so that the render method takes care of hiding and showing the NavBar, when you log in and out. I suggested maintaining a isLoggedIn state in your App component, and rendering the NavBar based on that, instead of directly accessing the SessionStorage. You could then use a custom event to update the state, when SessionStorage changes.
See this question for updating state based on Storage (in short, you fire and handle a custom event for storage changes): How to listen to localstorage in react.js
This might still be more code that you had hoped for, but it's more aligned with how React works, to derive the view (render) from component state.

How to render a component on each route with working functionality?

I'm trying to render a component that plays an mp3 on each route, and have the mp3 continue to play uninterrupted despite route changes. I'm not sure if this is possible, but i'd assume it is considering React creates single page sites.
So far i've only gotten as far as getting the component to render, my problem is all of the functionality along with the component has seized to work... I can't figure out why..
<HashRouter>
<Route path="/" render={props => <Music />} />
<Route exact path="/" component={Home} />
<Route path="/work" component={MyWork} />
<Route path="/about" component={About} />
</HashRouter>
The Route in question is the first one.
I've also tried this thinking I needed to pass down the props in order for the functionality to work
<Route path="/" render={props => <Music
changePlayingState={props.changePlayingState}
playing={props.state.playing}
showMusicMessage={props.showMusicMessage}
hideMusicMessage={props.hideMusicMessage}/>} />
A working version of the component(and how i would like it to appear on all routes) is shown on the home page(click play) code sandbox had a file size limit so i just added some popping sound effects(takes a second to start playing)
https://codesandbox.io/s/rlw1q45m1m
The only components regarding this question (and the only you'll need to view)are as follows:
Index.js
The routes: Home.js, About.js, MyWork.js
Music Component: Music.js
If any additional information is needed please ask! Thanks!
You just have to pull out the Music Component and make it a stand alone Component in itself then pass it down to the HashRouter within a single child and rest of the Routes.
Here is an example to implement NavBar in each route of the React App.

Advantages of dynamic vs static routing in React

I'm reading about static vs dynamic routing in React Router, and I'm struggling to identify the advantages of the latter (and why v4 chose to go with it). I can see the advantage of listing out all the routes for an application (static), as well as the component that each route maps to, allowing you to trace what would be rendered given a specific URL. But I'm not seeing any clear advantage to dynamic routes.
If anything, I can only see disadvantages, because there is no clear way to see what state a URL will map to, without starting at the root app element and working your way through the routes (though I might be mistaken).
What situations does dynamic routing address? Why is it preferable to static routing (maybe specifically in React apps)?
Dynamic Routing
From the react router docs:
When we say dynamic routing, we mean routing that takes place as your
app is rendering, not in a configuration or convention outside of a
running app.
Think of routes as components
The earlier versions of react-router (pre v4) used to have static routes. This led
to a centralized routing in apps like:
<Router>
<Route path='/' component={Main}>
<IndexRoute component={Home} />
<Route path='about' component={About} />
<Route onEnter={verifyUser} path='profile' component={Profile} />
...
</Route>
</Router>
However, this is not exactly the React way of doing things. React focuses on composition using components based logic. So, instead of imagining our Routes as a static system, we can imagine them as components, which is what react-router v4 brings in and the primary philosophy behind it.
Therefore, we can use Route as we would use any React component. This lets us add Route components as and when we build different components. One advantage of doing this is we can decouple the routing logic to the components needing them.
Nesting routes
The About component can handle all the routes and conditionally render parts of UI based on the url (say /about/job or /about/life etc).
Another thing to note is that a Route component will either render the component for a matching route or null. Example, the following Route renders the About component for a route /about and null (or nothing) otherwise.
<Route path='about' component={About} />
This is also similar to how we're used to conditionally rendering components in React:
route === '/about' ? <About /> : null
Now if we need to render some other components inside the About component for routes /about/job or /about/life we can do it like:
const About = ({ match ) => (
<div>
...
<Route path={`${match.url}/job`} component={Job} />
<Route path={`${match.url}/life`} component={Life} />
</div>
)
Dynamic imports and code splitting
Personally, I've also found this approach works better for me in case I'm using dynamic imports with code-splitting, since I can add dynamic routes in any of my components. For example,
import Loadable from 'react-loadable';
const Loading = () => (
<div />
);
const Job = Loadable({
loader: () => import('./Job'),
loading: Loading,
});
const Life = Loadable({
loader: () => import('./Life'),
loading: Loading,
});
...
render() {
return (
...
<Route path={`${match.url}/job`} component={Job} />
<Route path={`${match.url}/life`} component={Life} />
)
}
Responsive routes
Another great use case for dynamic routing is creating responsive routes which is explained beautifully in the react router docs and a recommended read. Here's the example from the docs:
const App = () => (
<AppLayout>
<Route path="/invoices" component={Invoices}/>
</AppLayout>
)
const Invoices = () => (
<Layout>
{/* always show the nav */}
<InvoicesNav/>
<Media query={PRETTY_SMALL}>
{screenIsSmall => screenIsSmall
// small screen has no redirect
? <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard}/>
<Route path="/invoices/:id" component={Invoice}/>
</Switch>
// large screen does!
: <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard}/>
<Route path="/invoices/:id" component={Invoice}/>
<Redirect from="/invoices" to="/invoices/dashboard"/>
</Switch>
}
</Media>
</Layout>
)
Summarizing the docs, you'll notice how simple and declarative it becomes to add the Redirect to large screen sizes using dynamic routing. Using static routing in such cases would be quite cumbersome and would need us to put all the routes in a single place. Having dynamic routing simplifies this problem since now the logic becomes composable (like components).
Static Routing
There are some problems which are not solved easily with dynamic routing. An advantage of static routing is that it allows for inspection and matching of routes before rendering. Hence it proves useful especially on server side. The react router team is also working on a solution called react-router-config, quoting from which:
With the introduction of React Router v4, there is no longer a
centralized route configuration. There are some use-cases where it is
valuable to know about all the app's potential routes such as:
Loading data on the server or in the lifecycle before rendering the next screen
Linking to routes by name
Static analysis
Hope this provides a good summary of both Dynamic Routing and Static Routing and the use cases for them :)
According to the React-Router docs:
When we say dynamic routing, we mean routing that takes place as your
app is rendering, not in a configuration or convention outside of a
running app. That means almost everything is a component in React
Router.
Its clear for the explanation that, all you Routes are not initialised at the start of your application,
In React-router v3 or below, it used static Routes and all Routes would be initialised at the top level, and nesting used to be achieved like
<Router>
<Route path='/' component={App}>
<IndexRoute component={Dashboard} />
<Route path='users' component={Users}>
<IndexRoute component={Home}/>
<Route path="users/:id" component={User}/>
</Route>
</Route>
</Router>
With this API setup, react-router was reimplementing parts of React (lifecycles, and more), and it just didn’t match the composition logic that React recommends on using.
With Dynamic Routes the following advatages, comes to be foreseen
Nested Routes
Nested Routes with Dynamic Routing are more like
const App = () => (
<BrowserRouter>
{/* here's a div */}
<div>
{/* here's a Route */}
<Route path="/todos" component={Todos}/>
</div>
</BrowserRouter>
)
// when the url matches `/todos` this component renders
const Todos = ({ match }) => (
// here's a nested div
<div>
{/* here's a nested Route,
match.url helps us make a relative path */}
<Route
path={`${match.path}/:id`}
component={Todo}
/>
</div>
)
In the above example, only when /todos matches the route-path, the Todo component is mounted and only then the Route path /todos/:id is defined.
Responsive routes
The React-router docs have a good use case for this.
Consider a user navigates to /invoices. Your app is adaptive to different screen sizes, they have a narrow viewport, and so you only show them the list of invoices and a link to the invoice dashboard. They can navigate deeper from there.
However on a large screen, navigation is on the left and the dashboard or specific invoices show up on the right.
and hence /invoices is not a valid Route for a large screen and we would want to redirect to /invoices/dashboard. This may so happen, the user rotates his/her phone from a portait to a landscape mode. This can easily be done using dynamic Routing
const Invoices = () => (
<Layout>
{/* always show the nav */}
<InvoicesNav/>
<Media query={PRETTY_SMALL}>
{screenIsSmall => screenIsSmall
// small screen has no redirect
? <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard}/>
<Route path="/invoices/:id" component={Invoice}/>
</Switch>
// large screen does!
: <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard}/>
<Route path="/invoices/:id" component={Invoice}/>
<Redirect from="/invoices" to="/invoices/dashboard"/>
</Switch>
}
</Media>
</Layout>
)
Using Dynamic Routes with React Router’s, think about components, not static routes.
Code Splitting
One great feature of the web is that we don’t have to make our visitors download the entire app before they can use it. You can think of code splitting as incrementally downloading the app. This is made possible with Dynamic Routing.
The advantages it brings is that all your code need not be downloaded at once and hence it makes initial rendering faster.
Here is a good article that helps you setUp codeSplitting for your application
Writing Composable Authenticated Routes
With Dynamic Routing its also made easier to write PrivateRoutes(an HOC that does authentication) which allow for authenticating users and providing them access to specific Routes and redirecting otherwise. This call all me made very generically
A Typical Private Route would be look like
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
and can be used as
<PrivateRoute path="/protected" component={Protected} />

React Router 4 Link To not updating url bar

Following the examples provided here https://reacttraining.com/react-router/examples/url-params.
I constructed my own route for my website like so:
<Router>
<div>
<Route path='/' exact component={Layout} />
<PublicRoute authed={this.state.authed} exact path='/login' component={Login} />
<PublicRoute authed={this.state.authed} exact path='/register' component={Register} />
<PrivateRoute authed={this.state.authed} path='/dashboard' component={Dashboard} />
<Route path='/cachaca/:bottle' component={Layout} />
<Route render={() => <h3>No Match</h3>} />
</div>
</Router>
That is pretty similar to the example given except for the fact that the navigation (or link to) is somewhere else in the page.
The route itself is working perfectly. I can get match information, i can sucessfully check whether a user is logged in or not and so forth.
My only problem is that the URL bar isn't changing to reflect the navigation changes, so if I am sitting at localhost:3000/cachaca/1234 and click to go home, the home page will load but the URL bar will still show 'localhost:3000/cachaca/1234'.
I've seen similar posts to mine from react router 2/3 that seem to indicate that the history prop is what's causing this issue - but I don't believe that to be the case in Router 4.
In addition to that, the example in the react training website works and mine (which seems to be following the same steps) does not.
Am I missing something here? Why am I not getting the same results as in the training website.
Here is the navigation link I am using:
<Link to={"/"}>Home</Link>
Appreciate any help,
Thanks!
Wow, this is silly - but I wasted my entire day in this so will post this in the hope of saving someone else some time:
Turns out it wasn't working because I was doing my testing on the webpack-dev-server/ folder and react-router 4 seems to have a problem with that.
As soon as I went from http://localhost:3000/webpack-dev-server/cachaca/asdf to http://localhost:3000/cachaca/asdf the website started behaving as it should - both in content and its location in the URL bar.
Happy coding to all!

Resources