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.
Related
Basically, I am trying to pass an id value and render the associated data. however, when I want to call the component to render data my route does not call the component.
I have 2 components. (Main and Teams)
Main Component
render() {
const OneTeam = ({match}) => {
console.log("Never logs this!");
return (
/*Let's assume I am returning simple HTML here */
<p>Hello</p>
);
};
return (
<Switch>
<Route
path="/"
component={() => <Teams teams={this.props.teams} />}
/>
<Route path="/:teamId" component={OneTeam} />
</Switch>
);
}
It never goes into OneTeam component.
Teams Component
<Link to={`/${team.id}`}>Details</Link>
I can pass the id value correctly.
What happens?
As you can see Teams component is my homepage. So here when I click a Team (there is no Team component, you can think of a card), I get the id and with <Link> I can push my id to URL (localhost:5001/98), however, nothing else happens. In the Main component,<Route path ="/:teamId" component={OneTeam} seems don't work at all because I cannot render OneTeam component, even I am not able to console.log in that component.
I am not sure I am missing something because I just try to do a very basic thing. By the way, I use Redux if it is helpful to figure out. Why I cannot go into OneTeam component or function.
You should swap the order in which the routes are called. It will render the first route it matches, which is your home and your detail view will never get evaluated. But if you switch the order, the detail view will get evaluated before that and if it matches, it will be rendered.
It is unusual to use a parameter on /, because it makes it difficult to extend your application to other pages and unnecessarily complex to distinguish whether any other route is a team id or a different page. You should do something like:
<Route
exact
path="/"
component={() => <Teams teams={this.props.teams} />}
/>
<Route exact path="/team/:teamId" component={OneTeam} />
and then
<Link to={`/team/${team.id}`}>Details</Link>
I have a simple react app I'm working on that utilizes react router. My apps starts out of app.js and has the following router setup in app.js:
<BrowserRouter>
<MuiThemeProvider theme={theme}>
<NavigationBar onLogout={self.userLoggedOut} loggedIn={self.state.loggedIn} />
<div className="content-area container-fluid">
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/login" component={LoginPage} />
<Route exact path="/page1" component={Page1} />
<Route exact path="/page2" component={Page2} />
</Switch>
</div>
</MuiThemeProvider>
</BrowserRouter>
In my app.js constructor, I have console.log to fire off a message to say the constructor was called. Initially I see the message fire off when first coming into the app and then if I use links to navigate between pages I do not see any follow up messages. However, if I go the browser address bar and type in a url manually (http://localhost:3000/login for example), the page loads successfully but have the message from the app.js constructor fire off again. How do you get a react/react-router app to accept manually entered urls the same as links so that the app doesn't reload?
In my app.js constructor, I have console.log to fire off a message to
say the constructor was called. Initially I see the message fire off
when first coming into the app and then if I use links to navigate
between pages I do not see any follow up messages
This is the general behaviour of react app. Very first time you call the link the your app.js enters the tree and it gets rendered. While rendering all lifecycle method including constructor fires in fixed sequence so you are able to see log but when you are changing the link then the component corresponding to route inside is unmounted and component related to new route is rendered and not the complete app.js. This is why you do not see log again.
How do you get a react/react-router app to accept manually entered
urls the same as links so that the app doesn't reload?
You cannot do so because when you are explicitly entering url in address bar then your browser doesnot relate the url with the running web application. It will always render your web application from the beginning. So when it is rendering from beginning you will always get that log.
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.
I recently reconfigured my React app, and it seems that I have broken the functionality of my parameterized routes. Rather than going too deep into how it used to work, I'll describe what I am currently stuck with and what I am aiming to achieve...
In a navigation panel on the side of my page, a user can view a list of links to resources - the urls for these links would be something like:
user/123
user/456
user/789
group/123
group/456
group/789
Clicking the first link will now render the User component in the main div on my page (rendering in {this.props.children} - see App.jsx below), and a call to componentDidMount() pulls data for the user with id 123 and populates the component. Ok, so far, so good.
However, if a user now clicks on the link for user/456, nothing happens. The url in the navbar changes, but the User component does not re-render, though clicking a link for a group will correctly clear out the User component and render the Group component instead... then, of course, I have the same problem with the groups, etc...
How can I force the re-rendering of a component when the pathname remains the same but the parameter has changed? If I've clicked the link for user/123 and then I click the link for user/456, I want that User component to re-render and pull the new data.
Other relevant code...
index.js
import { Router, Route, browserHistory } from 'react-router';
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="/user/:id" component={User} />
<Route path="/group/:id" component={Group} />
</Route>
</Router>
App.jsx
<div className="App">
<div className="body_section_container">
<div className="body_section main">
<Nav />
{this.props.children}
</div>
<div className="body_section sidebar">
<Search searchModel={this.searchAll} user_list={this.state.user_list} group_list={this.state.group_list} organizations_list={this.state.organizations_list} />
</div>
</div>
</div>
Try plugging into componentWillReceiveProps, which should fire when the route params change. Then you can receive the new route params, submit a new query, and update state.
In the Route component, specify a key.
<Route path={YOURPATH} render={(props) => <YourComp {...props} keyProp={id} key={id}/>} />
when react see a different key, it will trigger rerender.
I have a React MaterialUI AppBarcomponent with property title , that I am changing based on the value returned by window.location.pathname. So as the page/url changes, the title will change with it. Looks something like below:
<AppBar
title={this.renderTitle()}
/>
renderTitle() {
if (window.location.pathname === '/home'
return 'home';
} else if (window.location.pathname === '/login'
return 'login';
}
The issue I am running into is that renderTitle() does not get executed if a different component (so not the AppBar) causes the page/url change.
E.g. another separate React component on the page triggers the page to change, which I'd hoped with trigger renderTitle(), but it doesn't... thus the title property never updates. So if I am navigating from /home to /login, the following will happen:
pathname is /home
user presses a button which runs a function, submit(), which is used to change the page w/ react-router
renderTitle() is run at this point, but window.location.pathname is still returning the previous page
submit() changes the page to /login
window.location.pathname is now correctly set to /login, but it is too late as renderTitle() has already been run
any help is appreciated, thanks
The best way is to use react-document-title library.
From documentation:
react-document-title provides a declarative way to specify document.title in a single-page app.
This component can be used on server side as well.
If your component that renders AppBar is an actual route you can just read the pathname from the props that react router injects.
<Router>
<Route path="/" component={App}>
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message} />
</Route>
</Route>
</Router>
For example in App, About, Inbox and Message you have access to the router props. And you can also pass the props to their children.
render() {
return (
<AppBar
title={this.renderTitle(this.props.location.pathname)}
/>
);
}
And in your function just use the parameter to return the correct result. Now because you are using props your component will update automatically when they change.