I am trying to implement React-Router into an existing react app.
How can I use react-router to display components based on some conditions.
var displayRHS;
if(this.state.displayEventComponent){
{/*
* Events Menus
*/}
displayRHS = <DisplayEventComponent
parentFunc={this.displayComponentFunction}
parentPropDay={this.state.day}
/>
} else if (this.state.displayToDoListComponent){
{/*
* To Do List Menu
*/}
displayRHS = <DisplayToDoListComponent
parentCallback_2={this.updateDisplayToDoListComponent}
updateList={this.state.updateDisplayToDoListComponent}
displayIssuesNotList={false}
/>
} else if (this.state.displayIssuesComponent) {
{/*
* Issues menu
*/}
displayRHS = <DisplayIssuesComponent
parentCallback_2={this.updateDisplayToDoListComponent}
updateList={this.state.updateDisplayToDoListComponent}
displayIssuesNotList={true}
/>
}
Displaying Routes breaks
<Route exact path="/" component={displayRHS} />
How can I display these components with their respective props passed in as well?
Many Thanks in advance
PS, I am kind of thinking that a single page should be just that single page and using a routing library should be a sign that you should just have a page refresh instead..
A single page application is called "single page" because the client fetches only one HTML page from the server side. A single page application can have multiple "client-side pages".
The application you are migrating used some condition because it didn't have a router. In react-router, the condition is matching a URL.
The react router allows you to navigate to a client-side page. You will use a component called <Link> to navigate to a client-side page. A virtual page is just a React component. Each available route needs to define a Route. You will need one Route for each client-side page:
<Router>
<Route exact path="/" component={LayoutComponent}>
<IndexRoute component={ToDoPage} />
<Route path="/events" component={EventsPage} />
<Route path="/issues" component={IssuesPage} />
<Route/>
</Router>
The LayoutComponent will always be rendered:
class LayoutComponent extends React.Component {
render() {
return (
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/events">Events</Link></li>
<li><Link to="/issues">Issues</Link></li>
</ul>
{this.props.children}
);
}
}
The value of this.props.children will be the page that matches the URL. So if the URL is /issues the component rendered in {this.props.children} will be the IssuesPage because we configured it that way:
<Route path="/issues" component={IssuesPage} />
Each of your pages can then render the components
ToDoPage:
class ToDoPage extends React.Component {
constructor() {
this.state = {
updateDisplayToDoListComponent: []
};
}
render() {
return (
<DisplayToDoListComponent
parentCallback_2={this.updateDisplayToDoListComponent}
updateList={this.state.updateDisplayToDoListComponent}
displayIssuesNotList={false} />
);
}
public updateDisplayToDoListComponent() {
// do something
}
}
EventsPage:
class EventsPage extends React.Component {
constructor() {
this.state = {
day: 1
};
}
render() {
return (
<DisplayEventComponent
parentFunc={this.displayComponentFunction}
parentPropDay={this.state.day} />
);
}
public displayComponentFunction() {
// do something
}
}
IssuesPage:
class IssuesPage extends React.Component {
constructor() {
this.state = {
updateDisplayToDoListComponent: []
};
}
render() {
return (
<DisplayIssuesComponent
parentCallback_2={this.updateDisplayToDoListComponent}
updateList={this.state.updateDisplayToDoListComponent}
displayIssuesNotList={true} />
);
}
public updateDisplayToDoListComponent() {
// do something
}
}
This is not going to work out of the box and you are going to need to do some reading of the react-router docs but you should have enough details to figure out how to get it to work. You can also lear from "Getting Started with React Router".
Related
I'm working on the application with React + Firebase. The app requires authentication that users accesses to their own timeline. So I want the app separated between LoggedIn screens and LoggedOut screens.
In App.js, I set up <Auth /> to the screens require authentication. Like this:
function App() {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<Switch>
<Switch>
<Auth>
<Route path="/timeline" component={Timeline} />
</Auth>
</Switch>
<Route path="/" component={Home} />
</Switch>
</div>
</BrowserRouter>
</Provider>
);
}
export default App;
And added Auth.
import React from "react";
import { Redirect } from "react-router-dom";
import firebase from "../config/firebase";
class Auth extends React.Component {
constructor(props) {
super(props);
this.state = {
user: false
};
}
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.setState({ user });
}
});
}
render() {
if (!this.state.user) {
return (
<div>{this.state.user ? this.props.children : <Redirect to="/" />}</div>
);
}
}
}
export default Auth;
But it is not working. It has no error message but I can't access any components...
Router Issue
As mentioned in this thread, nested Switch components is not the intended way that react-router was designed and should probably be avoided.
I think you have overcomplicated the App component here. I would recommend something more like the below example because your routing logic doesn't need to care about authentication. You can wrap the Timeline component with the Auth component inside the implementation of Timeline, and then the router just does the routing, nothing else.
function App() {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<Switch>
<Route path="/timeline" component={Timeline} />
<Route path="/" component={Home} />
</Switch>
</div>
</BrowserRouter>
</Provider>
);
}
export default App;
An alternative, as mentioned in the linked thread above, is to write your own Switch logic. This might allow you to keep all of the authenticated routes all visible upfront, but you may not want to get into messing around with that.
You need an 'authenticating' state
As touched on in Soroush's answer, you also have an issue with the state management in the Auth component. At the moment, it goes like this:
Component renders with user = false
Redirect component is rendered and page redirects to '/'
Do you see how there is never an opportunity to render the child of auth properly for an authenticated user because the initial render will always trigger the redirect? Firebase never has a chance to log the user in before they are redirected.
To fix this, you Auth to load the page into some sort of 'authenticating' state and only redirect the user after you know whether they are logged in or not. Remember, onAuthStateChanged will only ever be triggered after the initial render.
Good luck!
In this case and your authentication structure I recommended to you using programmatically navigate.
import { withRouter } from "react-router";
class Auth extends React.Component {
constructor(props) {
super(props);
this.state = {
user: false,
authenticating: true,
};
}
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
if (!user) {
this.props.history.push('/login');
} else {
this.setState({ authenticating: false });
}
});
}
render() {
return (
<div>{authenticating ? <p>Authenticating...</p> : this.props.children}</div>
);
}
}
export default withRouter(Auth);
I build a project for a school’s records system, in which I build the front-end with React. On the main component of the admin page, I wanted to have a react-router which will navigate through the admin dialogs. As I tried to implement this, the following problem occurred: when trying to pass parameters to a class through the react route component, the child component receives no props.
I have the following react component hierarchy:
class Test extends React.Component {
constructor() {
super();
console.log("in class: " + this.props)
}
render() { return <div>test</div>}
}
class AdminPage extends BasicPage {
/* Other class functions here... */
render() {
let pageBody = "";
if(!this.state.isLoading)
pageBody = (
<Router>
<Switch>
<Route path={"/:schoolName/admin"} component={AdminMenu} exact/>
<Route path={"/:schoolName/admin/view/:id"} exact
component={() => <Test par1="abc" />} />
</Switch>
</Router>
);
return (
<Layout title={ this.state.isLoading ?
TITLE_UNTIL_LOADED :
PAGE_TITLE + this.state.schoolPrefs.heb_name}
subtitle={ this.state.subtitle }
notification={ this.state.notification }
isLoading={ this.state.isLoading }
>
{pageBody}
</Layout>
);
}
}
When I go to /Random Name/admin/view/someID, it prints to the console in class: undefined.
I then wanted to see if the problem is in the passing component or the receiving one. I defined the function otherTest(props) as follows:
function otherTest(props) {
console.log("Function props: " + props);
return (<Test {...props} />);
}
And then changed the route component like so:
<Route path={"/:schoolName/admin/view/:id"} exact
component={otherTest} />
When then I went to /Random Name/admin/view/someID, I saw that the function received the props just fine, but the log within <Test … /> still printed undefined.
I also tried adding <Test param1=”123” /> after the {pageBody} variable in the main render function, but it printed in class: undefined as well.
Does someone know where the problem might be?
Thanks.
You must take props parameter from constructor and then pass it to super.
constructor(props){
super(props);
}
Do not use this.props in constructor beacuse constructor fire only at the creating class moment.
Use this code:
constructor(props) {
super(props);
console.log(props);
}
I have a perplexing problem that I cant figure out. I am new to react + flux + react router.
I have the following routes:
<Router history={browserHistory} >
<Route path="/" component={App}>
<IndexRoute component={Search}/>
<Route path="/login" component={Login} />
<Route name="client" path="/client(/:clientid)" component={Client}/>
{/* <Route name="tel" path="/tel/:telid" component={Tel}/>*/}
</Route>
</Router>
I want to be able to pass specific params to specific routes. I know i can pass all params to all routes using:
{this.props.children && React.cloneElement(this.props.children, {...})}
however I dont want to allow sub components access to other component states. Ideally I'd like to pass the loginState to the login component, the searchState to the search component and the clientState to the client component. with the above method the client component can access all component states.
I currently have this workaround but it feels dirty and not very future proof:
var React = require('react');
var AppStore = require('../stores/AppStore');
var browserHistory = require('react-router').browserHistory;
// Flux cart view
var App = React.createClass({
getInitialState() {
return AppStore.getState();
},
componentWillMount() {
if (!this.state.auth.loggedIn) {
browserHistory.push('/login');
}
},
// Add change listeners to stores
componentDidMount() {
AppStore.addChangeListener(this._onChange);
},
// Remove change listers from stores
componentWillUnmount() {
AppStore.removeChangeListener(this._onChange);
},
// Method to setState based upon Store changes
_onChange(){
this.setState(AppStore.getState());
},
// Render cart view
render() {
console.log(this.props.children);
var name = this.props.children.type.displayName;
var p;
switch(name) {
case 'client':
p = {clientState: 'test'}
break;
case 'login':
p = {auth: this.state.auth}
break;
}
return (
<div>
{this.props.children && React.cloneElement(this.props.children, p)}
</div>
);
}
});
module.exports = App;
Any thoughts on how to achieve this correctly? I have looked at a lot of google results but most harken back to the old versions of react and react-router.
you can use named components
...
<Route path="/login" components={{login:Login}} />
<Route path="/client(/:clientid)" components={{client:Client}}/>
...
And in App component
class App extends Component {
constructor(props){
super(props);
this.setProps= this.setProps.bind(this);
}
setProps(comp, props){
if(!comp) return null;
return React.cloneElement(comp, props);
}
render(){
const {client, login}=this.props;
const {clientData, loginData}=this.state;
return (<div>
{this.setProps(client,clientData)}
{this.setProps(login,loginData)}
</div>);
}
}
export default App;
it also usefull when on page more then one component per route
I am running into a problem because I have a fairly complex component that renders a list of items. The list of items is taken directly from the redux state tree. I want to have 2 separate routes that reuse this same component, but each of them will connect to a different list from the state tree.
Here is a simplified example:
Starting with the state tree:
state = {
fruits: ['apple', 'banana', 'grape', 'pear'],
vegetables: ['celery', 'carrot', 'cucumber']
}
and a simple listing component
class ListView extends Component {
render() {
return (
<div>
{this.props.items.map((item, index) =>
<div key={index}>{item}</div>
)}
</div>
)
}
}
function mapStateToProps(state) {
return {
items: state.fruits
}
}
export default connect(mapStateToProps)(ListView)
The higher level App component looks like this:
class App extends Component {
render() {
return (
<div>
{this.props.children}
</div>
)
}
}
export default connect()(App)
And the routes look like this:
<Route path='/' component={App}>
<IndexRedirect to='fruits' />
<Route path='fruits' component={ListView} />
<Route path='vegetables' component={ListView} />
</Route>
So right now the ListView component is connected to the fruits part of the redux state. However I would like to utilize the route structure here so '/fruits' and '/vegetables' will both use the same component, but '/fruits' will list the fruits while '/vegetables' will list the vegetables.
The obvious work around is to create a completely new and identical component class and change the mapStateToProps method to connect to the vegetables array.
Is there a way to reuse this component but have access to different parts of the state tree in each?
From your ListView, you could export both FruitsList and VeggiesList as separate components, then display the correct one based on route.
class ListView extends Component {
render() {
return (
<div>
{this.props.items.map((item, index) =>
<div key={index}>{item}</div>
)}
</div>
)
}
}
function mapStateToPropsVeggies(state) {
return {
items: state.veggies
}
}
function mapStateToPropsFruits(state) {
return {
items: state.fruits
}
}
const VeggiesList = connect(mapStateToPropsVeggies)(ListView);
const FruitsList = connect(mapStateToPropsFruits)(ListView);
export default ListView;
export {
VeggiesList,
FruitsList
};
Then update the router to:
<Route path='/' component={App}>
<IndexRedirect to='fruits' />
<Route path='fruits' component={FruitsList} />
<Route path='vegetables' component={VeggiesList} />
</Route>
I'm trying to learn React by building a very basic "portfolio" site with react-router. My main components are: App, Work, Project and ProjectDetail. On the 'Work' page you should be able to see all of the project thumbnails and titles. Clicking on a specific project should route you to that project's detail page, which will include the rest of the project's details. How can I pass the props of Project to ProjectDetail?
My files:
main.js
/*
Routes
*/
var routes = (
<Router history={createHistory()}>
<Route path="/" component={App}>
<IndexRoute component={Work} />
<Route path="work" component={Work} />
<Route path="work/:id" component={ProjectDetail} />
<Route path="about" component={About} />
</Route>
</Router>
);
ReactDOM.render(routes, document.getElementById('main'));
-
App.js
/*
App
*/
class App extends React.Component {
render() {
return (
<div>
<h1>App</h1>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/work">Work</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
{this.props.children}
<footer>footer</footer>
</div>
)
}
}
export default App;
-
Work.js
class Work extends React.Component {
render() {
return (
<div>
<p>Work</p>
<ul>
{/* Need to loop of all projects */}
{PROJECTS.map(project => (
<li key={project.id}>
<Project project={project} />
</li>
))}
</ul>
</div>
)
}
}
export default Work;
-
Project.js
class Project extends React.Component {
render() {
var project = this.props.project;
var linkTo = "/work/" + project.id;
return (
<Link to={linkTo}>
{project.title}
<span> </span>
{project.type}
</Link>
)
}
}
export default Project;
-
ProjectDetail.js
class ProjectDetail extends React.Component {
render() {
return (
<div>
{/* THIS IS WHAT INFORMATION I NEED */}
{this.props.project.title}
{this.props.project.description}
{this.props.project.type}
{this.props.project.technologies}
</div>
)
}
}
export default ProjectDetail;
I can access the data in my Project component, but how can I pass that along to ProjectDetail since I never explicitly use the ProjectDetail component (only used in my routing)?
EDIT: I should add that I suppose I am technically not trying to pass the props to a child since Project is no longer rendered once you are routed to a ProjectDetail. Does that make this impossible?
You need to use the createElement handler for your routes to control the instancing of the route handler. There you can pass props around to the components. React-Router decouples mother-child components, any state propagation needs to be done in the routes.
Update
Based on your edit, in your case ProjectDetail must re-query the applicable project using this.props.params.id, something like...
ProjectDetail.js
class ProjectDetail extends React.Component {
render() {
let project = PROJECTS.find((item)=>item.id == this.props.params.id);
return (
<div>
{project.title}
{project.description}
{project.type}
{project.technologies}
</div>
)
}
}