I'm new to React and still having some trouble "thinking in react" when it comes to global events & scope.
My goal is to maintain some global data about the "session" like whether a user is logged-in and their identity.
My main container looks like this:
import React, { Component } from "react";
import { Link, Route, Switch } from 'react-router-dom';
import Header from './Header';
import Routes from "./Routes";
class App extends Component {
render() {
return (
<div className="parent-container">
<Header />
<Content />
</div>
)
}
}
class Content extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="content" >
<Routes />
</div>
)
}
}
export default App;
<Header /> is simply a nav bar with a title & links.
<Routes /> is a react-router which looks like this:
import React from "react";
import { BrowserRouter, Route } from 'react-router-dom';
import HomeView from "../presentational/_Views/HomeView";
import LoginView from "../presentational/_Views/LoginView";
import CreateUserView from "../presentational/_Views/CreateUserView";
import TestView from "../presentational/_Views/TestView";
const Routes = () => (
<div>
<Route exact path="/" component={HomeView} />
<Route path="/login" component={LoginView} />
<Route path="/test" component={TestView} />
<Route path="/create" component={CreateUserView} />
</div>
)
export default Routes;
Each route loads a "presentational" view component that contains child components for that particular view (Login view component imports a LoginForm component etc).
Whenever my app is hard-loaded (or reloaded) I would like the following logic to be fired (pseudo-code):
if session_token exists:
- make a call to API
- verify session
- store a global session object containing session data
I figured I can do something like this in a componentWillMount() function on the App component...
//...
class App extends Component {
componentWillMount(){
if(token && valid(token)){
// store the user session in an object containing token, user-role, etc.
}
}
render() {
//....
..but this seems like a bad design.
The other part to this is to:
show/hide navbar links based on whether a user is logged in (show "login" link if not logged in, show "log out" link is logged in)
check authentication on state change before the view is rendered.
The above two is where I wanna go for context but focusing only on the main issue here just to wrap my head around it.
How is something like this done "the React way"?
Related
In this case, there is an App component which has a Header component which renders one header if the user is logged in and one if not. There is also an Access component which renders either a Landing component if user is not logged in or Dashboard if the user is logged in. The user has access to all routes if logged in. How do I render components using react-router-dom if the user is on the Dashboard component? Currently, LeftNav should always be in view while the components in the main-content className toggle based on the route. Currently only the LeftNav and MainContent components work on "/", if navigated to /test or /test/new neither the LeftNavorTestComponentrender, however theHeadercomponent is still rendering correctly. What am I doing wrong here or how is this toggling betweenmain-content` components achieved?
import { BrowserRouter, Route } from "react-router-dom";
import Header from "./Header";
import Access from "./Access";
class App extends Component {
render() {
return (
<div>
<BrowserRouter>
<div>
<Header />
<Route exact path="/" component={Access} />
</div>
</BrowserRouter>
</div>
);
}
};
export default App;
////////////////////////////////
import Landing from "./Landing";
import Dashboard from "./Dashboard";
class Access extends Component {
renderContent() {
switch (this.props.auth) {
case null:
return;
case false:
return (
<Landing />
);
default:
return (
<Dashboard />
);
}
}
render() {
return (
<div>
{this.renderContent()}
</div>
);
}
}
export default Access;
////////////////////////////////
import { Switch, Route } from "react-router-dom";
import LeftNav from "./dashboard/LeftNav";
import MainContent from "./dashboard/MainContent";
import TestContent from "./dashboard/TestContent";
import TestContentNew from "./dashboard/TestContentNew";
class Dashboard extends Component {
render() {
return (
<div>
<div className="dashboard-wrapper" style={dashboardWrapper}>
<LeftNav />
<div className="main-content">
<Switch>
<Route path="/" component={MainContent} />
<Route path="/test" component={TestContent} />
<Route path="/test/new" component={TestContentNew} />
</Switch>
</div>
</div>
</div>
);
}
};
export default Dashboard;
Issue
The main Route in your application only ever matches and renders a route when it exactly matches "/", so when you navigate to another path it ceases to match.
Solution
I don't see where you pass auth as a prop to Access, but since it handles authentication and renders your actual routes you can simply just render it instead of a Route in App. It will always be rendered by the router and display either the landing page or the dashboard.
import { BrowserRouter, Route } from "react-router-dom";
import Header from "./Header";
import Access from "./Access";
class App extends Component {
render() {
return (
<div>
<BrowserRouter>
<div>
<Header />
<Access />
</div>
</BrowserRouter>
</div>
);
}
};
Trying on simple navigation with react-router-dom. Managed to have it works with this.props.history.push('/pathName'); However, I have doubts about my understanding and implementation, appreciate your 2 cents on correcting my mistakes.
Am I in the right direction?
My use case scenario:
Firstly, I have 3 pages with a common component <Navbar> implemented on top of each page. Navbar consists of 2 buttons Page A and Page B. User should be navigated to either screen when the respective button is clicked.
Homepage
Page A
Page B
My Implementation:
Homepage.js - (Besides the class name is different, both Page A and B has the same implementation)
import React from 'react';
import { Redirect } from "react-router-dom";
import Navbar from '../common/navbar';
class Homepage extends React.Component{
callBackFromNavBar = (value) => {
NavigationHelper.historyPush(this.props, value);
}
render(){
return (
<div>
<Navbar callback={this.callBackFromNavBar}/>
</div>
);
}
}
export default Homepage;
NavigationHelper.js
export const historyPush = (props,value) => {
console.log('helper calling');
switch(value){
case 'PageA':
props.history.push('/PageA');
break;
case 'PageB':
props.history.push('/PageB');
break;
default:
break;
}
}
Navbar - Following shows how the value is being pass back to parent
<Button variant="contained" onClick={ () => {
props.callback('PageA');
} }>Page A</Button>
Learning source:
https://dev.to/projectescape/programmatic-navigation-in-react-3p1l
Link to Sandbox
https://codesandbox.io/embed/react-router-dom-navigation-4tpzs?fontsize=14&hidenavigation=1&theme=dark
I would try something like this to simplify things
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import PageA from './'
import PageB from './'
function App() {
return (
<Router>
<Navbar />
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/pageA" component={PageA} />
<Route exact path="/pageB" component={PageB} />
</Switch>
</Router>
);
}
And then in your Navbar component just setup the <Link> to each of those pages, here i have setup the routes in the App.js file however i have seen people just use a dedicated component for routing but find what works best for you
i am making a login page that redirect the user after a successful login to home page i am using react router dom i tried to look for a simple way to do it but i couldn't find :
import Authen from './Pages/Authen';
import Home from './Pages/Home';
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom';
class App extends Component {
render() {
return (
<div className="App">
<Router>
<div>
<ul>
</ul>
<Route exact path="/" component={Authen}/>
<Route path="Home" component={Home}/>
</div>
</Router>
</div>
thank you for your help i really appreciate it :)
login page that redirect the user after a successful login to home
page
Use the withRouter higher order component that comes with react-router-dom. It will give your component acess to the history prop. With the history prop you can push to any new URL.
import React from 'react'
import {withRouter} from 'react-router-dom'
class Authen extends React.Component {
onLogin = () => {
// also other authentication code
this.props.history.push('/home')
}
render() {
return (
<button onClick={() => this.onLogin()}> Login </button>
)
}
}
export default withRouter(Authen);
I need to navigate to a route after an event is successful.
This seems to have changed since previous versions.
Previously we would do this:
import { browserHistory } from 'react-router';
...
handleClick(){
doSomething();
browserHistory.push('/some/path');
}
This example works in react-router and react-router-dom v4.0.0.
We have 3 components:
App.js - holds both Component 1 and Component 2
Component1.js - not wrapped in a Route and will always be rendered, but this will not have a reference of the "route props" - (history, location, match...)
Component2.js - rendered only if the route location match. Important thing to note that this component will be rendered with "route props"
To navigate programmatically, you can use react-router history object.
this.props.history.push('path');
This will work right off the bat for components rendered via Route, as these components will already have access to the route props (history). In our example this is Component2. However, for components that are not rendered via a Route (e.g. Component1), you would need to wrap it in withRouter to give you access to the history object.
App.js
import React, { Component } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
import Component1 from './Component1';
import Component2 from './Component2';
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Component1 />
<Route path="/render1" render={() => <div>Rendered from Component1</div>} />
<Route path="/" component={Component2} />
<Route path="/render2" render={() => <div>Rendered from Component2</div>} />
</div>
</BrowserRouter>
)
}
}
export default App;
Component1.js
import React from 'react';
import { withRouter } from 'react-router';
class Component1 extends React.Component {
handleButtonClick() {
this.props.history.push('/render1');
}
render() {
return (
<div>
<h1>Component 1</h1>
<button onClick={this.handleButtonClick.bind(this)}>Component 1 Button</button>
</div>
);
}
}
const Component1WithRouter = withRouter(Component1);
export default Component1WithRouter;
For Component1, we wrapped it in withRouter, and then exported the returned wrapped object. Some gotcha, notice that in App.js, we still reference it as Component1 instead of Component1WithRouter
Component2.js
import React from 'react';
class Component2 extends React.Component {
handleButtonClick() {
this.props.history.push('/render2');
}
render() {
return (
<div>
<h1>Component 2</h1>
<button onClick={this.handleButtonClick.bind(this)}>Component 2 Button</button>
</div>
);
}
}
export default Component2;
For Component2, the history object is already available from this.props. You just need to invoke the push function.
If you are using "function components" and Hooks, instead of expecting props, use the useHistory() function instead:
import {useHistory} from 'react-router-dom';
export default function MyFunctionComponent() {
const history = useHistory();
const myEventHandler = () => {
// do stuff
history.push('/newpage');
};
// ...
}
Just render a redirect component:
import { Redirect } from 'react-router';
// ...
<Redirect to="/some/path" />
See docs here: https://reacttraining.com/react-router/core/api/Redirect
I really appreciate CodinCat's answer since he helped me resolve a different error, but I found a more correct solution:
In React Router 4.0 (I don't know about previous versions) the router passes a history object to the component (i.e.: this.props.history). You can push your url onto that array to redirect:
this.props.history.push('/dogs');
In my case though, I had two levels of components, the router called a component called LoginPage, and LoginPage called a component called Login. You'll only have the history object in the props of your child object if you pass it on:
<Router>
<Route path="/dogs" component={DogsPage}/>
<Route path="/login" component={LoginPage}/>
</Router>
const LoginPage = (props) => {
// Here I have access to props.history
return (
<div>
<Login history={props.history} />
</div>
)
}
const Login = (props) => {
function handleClick(){
// Now we can simply push our url onto the history and the browser will update
props.history.push('/dogs');
}
return (
<div>
<button onClick={handleClick}>Click to Navigate</button>
</div>
)
}
I know the example above is a bit contrived, since it would be easier in this case to just use a link, but I made the example this way to keep it concise. There are many reasons you may need to navigate this way. In my case, I was doing a graphQL request and wanted to navigate to the home page once a person had successfully logged in.
I'm failing at passing a property from a <Route />
Here is some code :
./app.jsx (main app)
import React from 'react'
import { render } from 'react-dom'
import { Router, Route, IndexRoute } from 'react-router'
import App from './components/app'
import Home from './components/home'
import About from './components/about'
render((
<Router>
<Route path="/" component={App}>
<IndexRoute component={Home} title="Home" />
<Route path="about" component={About} title="About" />
</Route>
</Router>
), document.getElementById('app'))
./components/app.jsx
import React from 'react';
import Header from './template/header'
class App extends React.Component {
render() {
return (
<div>
<Header title={this.props.title} />
{this.props.children}
</div>
)
}
}
export default App
./components/template/header.jsx
import React from 'react'
class Header extends React.Component {
render() {
return (
<span>{this.props.title}</span>
)
}
}
export default Header
When I click on my home route* I want my Header component to display Home.
When I click on my about route I want my Header component to display About.
At this point, my Header components displays nothing. this.props.title is undefined in my App component.
Looks like you can't pass an attribute from a <Route />
Is there a way to achieve this?
Or is there another way? For instance, can you get something from the children element (this.props.children.title or something like that) ?
It looks like the route injects a routes property with a list of the matching routes. The last route in the list has the props you specify. See http://codepen.io/anon/pen/obZzBa?editors=001
const routes = this.props.routes;
const lastRoute = routes[routes.length - 1];
const title = lastRoute.title;
I'd hesitate a little to use this, since routes is not documented in the Injected Props, so I don't know how reliable it is across version updates. A simpler option, though not as legible, would be to use this.props.location.pathname and maintain a lookup table for titles.
The best and most flexible option is probably the boilerplate-heavy one, where you define the template and then reuse it across components:
class Template extends React.Component {
render() {
return (
<div>
<Header title={this.props.title} />
{this.props.children}
</div>
)
}
}
class About extends React.Component {
render() {
return (
<Template title="About">
Some Content
</div>
)
}
}