How to render a nested component with react-router - javascript

This is a noob question. I'm developing a universal react application using react-isomorphic-starterkit boilerplate (https://github.com/RickWong/react-isomorphic-starterkit). I would create a "fixed" sidebar on the left that contains links to the other pages rendered on a child of the main container on the right. Here's my code:
routes.js
module.exports = (
<Router history={browserHistory}>
<Route path="/" component={Main}>
<Route path='/inbox' component={Inbox} />
</Route>
</Router>
);
Main.js
export default class Main extends React.Component {
constructor(props, context){
super(props,context);
console.log('Main props', props);
console.log('Main context', props);
}
/**
* componentWillMount() runs on server and client.
*/
componentWillMount () {
if (__SERVER__) {
console.log("Hello server");
}
if (__CLIENT__) {
console.log("Hello client");
}
}
/**
* Runs on server and client.
*/
render () {
return (
<App/>
)
}
}
App.js
class App extends React.Component{
constructor(props, context) {
super(props, context);
console.log('App props ', props);
}
render(){
return (
<div>
<Sidebar/>
<RightContent />
</div>
)
}
}
RightContent.js
class RightContent extends React.Component{
render(){
return (
<div id="b" style={{backgroundColor:"#EEF0F4", width:"100%"}}>
<NavBar />
{this.props.children}
</div>
)
}
}
Problem is: Inbox component (a simple <div>) is not rendering when I click on the sidebar with <Link to="..."> tag. I don't know if the following thing is correct: as you can see, in App.js class constructor prints props variable...but the output is undefined. However, in Main.js props are printed correctly. react-router version is greater than 2. Could anyone help me?

You're not passing props down to App, so unless you use the location context, it won't have them.
You should do...
render () {
return (
<App {...this.props}/>
)
}
Then, you should have access to this.props.children in App to render the nested route(s). You'll need to specify that in RightContent too...
render(){
return (
<div>
<Sidebar/>
<RightContent>
{this.props.children}
</RightContent>
</div>
)
}
See the tutorial in the docs... https://github.com/reactjs/react-router/blob/master/docs/Introduction.md#with-react-router

Related

React - Giving a function to my Child Component

I'm struggling to update the state of my Parent component from the Child. To do that, I want to pass as argument to my child a function that update the parent's state:
export class Login extends Component {
updateTurn() {
console.log("UPDATE")
this.setState(
prevState => {
UPDATING
}
);
}
render() {
ROUTING MANAGEMENT
<Route path="/play/pledge">
<Pledge data={this.state.data} updateTurn={this.updateTurn}/>
</Route>
ROUTING MANAGEMENT
}
}
export class Game extends Component {
constructor(props) {
super(props);
this.data = this.props.data;
}
render() {
return (
<div>
<div className={"game"}>
{ this.props.children }
</div>
<div className={"next"}>
<Link to={Launcher.getRandomGame()}>
<button className={"button"} onClick={this.props.updateTurn}>Next</button>
</Link>
</div>
</div>
)
}
}
Unfortunately, nothing happened, even to the console.log()
<Pledge data={this.state.data} updateTurn={this.updateTurn}/>
The problem is that you are passing the function to some component named Pledge, but you should use Game instead:
<Game data={this.state.data} updateTurn={this.updateTurn}/>

Switch between components inside of an already routed component in react

I have used react-router to switch between 2 pages from the navbar. One of the pages is Home component. Now I have 3 columns as components in the homepage. Left column, middle column, and right column. By a button in the left column, I want to switch between two components in the mid column of the homepage.
function App() {
return (
<div className="App">
<Router>
<Navbar/>
<Switch>
<Route exact path="/">
<Home/>
</Route>
<Route path="/FAQ">
<FAQ/>
</Route>
</Switch>
</Router>
</div>
);
}
At home components ->
class Home extends Component {
render() {
return (
<div className={style.homeFlex}>
<LeftContainer/>
**<MidContainer/>**
**<AnotherMid/>**
<RightContainer/>
</div>
</div>
)
}
}
export default Home;
From a button inside LeftContainer, I want to switch between MidContainer and AnotherMid.
How can I use the router inside of an already routed component Linked from a sibling component? If there is a better way other than using the router to achieve the same, please share that also.
You don't need a route in this situation. You can use a state to render different components in your Home component.
Suppose your LeftContainer component looks like this:
class LeftContainer extends React.Component {
render() {
return (
<div>
<button onclick={() => this.props.handleSwitchComponent()}>
click here to change component
</button>
</div>
);
}
}
You'll need to pass a function through props, then you can use this component in Home component like this:
class Home extends Component {
state = { IsMidContainerVisible: true }
handleSwitchComponent = () => {
this.setState(({ IsMidContainerVisible }) => ({
IsMidContainerVisible: !IsMidContainerVisible;
}));
};
render() {
return (
<div className={style.homeFlex}>
<LeftContainer handleSwitchComponent={this.handleSwitchComponent} />
{/* Now here we need to specify when we want to show components */}
{this.state.IsMidContainerVisible ? <MidContainer /> : <AnotherMid />}
<RightContainer />
</div>
);
}
}
export default Home;

React pass object by routing?

What I want to do is set routing to my object. For example:
I set routing for:
http://localhost:3000/projects
It displays all my projects list (it works pretty ok)
Then I want to choose project and see details, but it doesn't work properly:
http://localhost:3000/projects/3
How it looks like:
When I click to Details button, it sends me to /projects:id but I get an error, that items in project are undefined.
My code. I store all routing in main App.js file:
class App extends Component {
render() {
return (
<div>
<BrowserRouter>
<div>
<Route exact path="/projects" component={ProjectsList} />
<Route exact path="/projects/:id" component={ProjectDetails} />
</div>
</BrowserRouter>
</div>
);
}
}
export default App;
I have ProjectsList.js component (i will include code of it if needed), in ProjectsList.js i have listgroup with Project.js that looks like this:
class Project extends Component {
render() {
return (
<ButtonToolbar>
<ListGroupItem>{this.props.project.name</ListGroupItem>
<Link to={`/projects/${this.props.project.id}`}>
<Button>Details</Button>
</Link>
</ButtonToolbar>
);
}
}
export default Project;
By Link to my browser pass me to proper URL (projects/2... etc) but i dont know how to pass object of project to ProjectDetails.js component. Code of it below:
class ProjectDetails extends Component {
render() {
return <li>{this.props.project.description}</li>;
}
}
export default ProjectDetails;
Could you tell me, how to pass object of project by Link to into ProjectDetails.js? For now, i get description as undefined (its obviouse because i pass nothing to component with details).
Use the render method in your route and pass the props.
<Route exact path="/projects/:id" render={() => (
<ProjectDetails
project = {project}
/>
)}/>
You need to use mapStateToProps here. and wrap your component in the conncet from react-redux.
It should be like:
import {connect} from 'react-redux';
class ProjectDetails extends Component {
render() {
return <li>{this.props.project.description}</li>;
}
}
function mapStateToProps(state, ownProps){
const projectInstance = DATA //the Data you are getting or fetching from the ID.
return { project : projectInstance }
}
export default connect((mapStateToProps)(ProjectDetails))
This is what it will look like..!!
class Project extends Component {
render() {
return (
<ButtonToolbar>
<ListGroupItem>{this.props.project.name</ListGroupItem>
<Link to={`/projects/${this.props.project.id}`}>
<Button>Details</Button>
</Link>
</ButtonToolbar>
);
}
}
function mapStateToProps(state, ownProps){
return { project : { id: ownProps.params.id } }
}
export default connect((mapStateToProps)(Project))

How to link to current page with different param in react router?

Let's say I have the following routes set up (just an example, actual routes aren't this messy):
<Router>
<Switch>
<Route path="/app/fun/:userid/profile" component={Profile} exact/>
<Route path="/photos/:userid" component={Photos} exact/>
<Route path="/:userid/contact" component={Contact} exact/>
</Switch>
</Router>
From ANY of the pages above, how can I link to the SAME page, but with a different userid?
eg:
/**
* Shared component that's rendered by Profile, Photos and Contact
*/
class SharedComponent extends React.Component {
render() {
const bobId = 423423432;
return (
<div>
<Link to={/* what to put here? */}>
Redirect to the same page but with Bob's userid
</Link>
</div>
);
}
}
export default withRouter(SharedComponent);
Note: using react router v4
Actually, I found a way to do this. Not sure if it's the best way, but it works:
/**
* Shared component that's rendered by Profile, Photos and Contact
*/
class SharedComponent extends React.Component {
render() {
const { location, match } = this.props;
const bobId = 423423432;
const bobUrl = location.pathname.replace(match.params.userid, bobId);
return (
<div>
<Link to={bobUrl}>
Redirect to the same page but with Bob's userid
</Link>
</div>
);
}
}
export default withRouter(SharedComponent);
Essentially, I'm taking the current url (location.pathname) and replacing the current userid (match.params.userid) with the new id (bobId)
Pass userId with path props to shared component like below,
class Profile extends React.Component {
render() {
const bobId = 423423432; // Pass bobId with path from parent to child
return (
<SharedComponent path={"/stuff1/stuff2/`${bobId}`/profile"} />
);
}
}
class SharedComponent extends React.Component {
render() {
return (
<div>
<Link to={this.props.path}>
Redirect to the same page but with Bob's userid
</Link>
</div>
);
}
}
export default withRouter(SharedComponent);

React toggle class + CSS transitions, not working... why?

I've been using classes to control open/close behaviors w/ a CSS transition for effect. I've used this on other components, no problem, but for some reason the same method is failing me in this scenario...
The open/close behaviors attach (I see the end difference w/ background color and translateY) but the CSS transition itself is lost... any ideas why I lose my CSS transition but everything else is working as expected?
Note, when I manually toggle the open/closed classes using Developer Tools, it works just fine! The CSS transition picks up!
So what's up with the React on click to toggle a class applying, but losing the CSS transition?
class Projects extends React.Component {
/* constructor, etc... */
render() {
return (
<div className="projects-nav-container">
<div className="center title monospace" onClick={this.props._toggleProjectNav} id="Menu">Menu</div>
<ul className={`projects-nav ${this.props._isProjectNavOpen ? 'open' : 'closed'}`}>
{ PROJECTS.map((project, index) =>
<li key={index} >
<p>project here</p>
</li>
) }
</ul>
</div>
);
}
}
App.js looks as such:
class App extends React.Component {
constructor() {
super();
this.state = {
_isProjectNavOpen: true
}
this._toggleProjectNav = this._toggleProjectNav.bind(this);
}
_toggleProjectNav() {
this.setState(prevState => ({
_isProjectNavOpen: !prevState._isProjectNavOpen,
}));
}
render() {
<div>
<Router>
<Route path="/projects" component={(props, state, params) =>
<Projects
_toggleProjectNav={this._toggleProjectNav}
_isProjectNavOpen={this.state._isProjectNavOpen}
{...props} />} />
</Router>
</div>
}
}
SCSS:
.projects-nav {
#include transition(all $transition_speed ease);
&.open {
#include transform(translateY(0));
background: red
}
&.closed {
#include transform(translateY(-100vh));
background: green;
}
}
It is because of react-router think of each route as a case in the switch statement, and the path in the <Route /> component being a key for that case. When the path gets changed the component is unmounted completely. Hence you don't see the CSS transitions because the DOM for it doesn't exist anymore.
If you want to animate with react-router. You need to use a react utility library called react-transition-group. Here is a detailed example by the author of react-router which you can follow. React Router Animation Example
I hope this helps.
Also there is this great talk on youtube for about 30 minutes that talks about how to do really nice animations in react with routing https://www.youtube.com/watch?v=S3u-ccn4PEM Cheers :)
Indeed, the problem is that react-router is unmounting your component and mounting it again with the new classes, losing the CSS transition in the process. To solve this issue, simply use render instead of component on the <Route> component.
As to why this works, from react-router documentation:
Instead of having a new React element created for you using the component prop, you can pass in a function to be called when the location matches. The render prop receives all the same route props as the component render prop.
For a more detailed explanation, you could read the question react router difference between component and render.
In summary, App.js should look like this:
class App extends React.Component {
constructor() {
super();
this.state = {
_isProjectNavOpen: true
}
this._toggleProjectNav = this._toggleProjectNav.bind(this);
}
_toggleProjectNav() {
this.setState(prevState => ({
_isProjectNavOpen: !prevState._isProjectNavOpen,
}));
}
render() {
<div>
<Router>
<Route path="/projects" render={(props, state, params) =>
<Projects
_toggleProjectNav={this._toggleProjectNav}
_isProjectNavOpen={this.state._isProjectNavOpen}
{...props} />} />
</Router>
</div>
}
}
I created a CodeSandbox using render and it seems to work properly!
Cheers!
Change key have to update element.
Try this code:
class App extends React.Component {
constructor() {
super();
this.state = {
_isProjectNavOpen: true,
_ProjectsKey: 1,
_RouteKey: 1
}
this._toggleProjectNav = this._toggleProjectNav.bind(this);
}
_toggleProjectNav() {
this.setState(prevState => ({
_isProjectNavOpen: !prevState._isProjectNavOpen,
_ProjectsKey: prevState._ProjectsKey + 1,
_RouteKey: prevState._RouteKey + 1
}));
}
render() {
<div>
<Router>
<Route key={this.state._RouteKey} path="/projects" component={(props, state, params) =>
<Projects
_toggleProjectNav={this._toggleProjectNav}
_isProjectNavOpen={this.state._isProjectNavOpen}
key={this.state._ProjectsKey}
{...props} />} />
</Router>
</div>
}
}

Categories