user1177440
user1177440

Reputation:

React-Router 1.0.0RC1 - Passing state as properties to child routes

I am working off Alex Bank's "Building a Polling App with Socket IO and React.js" (Lynda.com) , but I am trying to upgrade it to react-router 1.0.0-RC1.

Solution below, just skip all this

Please do not send me to documentation, it is not working for me, I guess I must be too thick to understand the documentation's "pithiness".

I have a main APP with 3 child routes, (Audience, Speaker & Board).

My code so far:

APP.js

import React, { Component } from 'react';
import io from 'socket.io-client';
import Header from './parts/Header';
import Routes from '../router/routes';
import { createHistory, useBasename } from 'history';

const history = useBasename(createHistory)({
  basename: '/'
});

export default class APP extends Component {

  constructor() {
    super();
    this.state = ({
      status: 'disconnected',
      title: ''
    });
  }

  componentWillMount() {
    this.socket = io('http://localhost:3000');
    this.socket.on('connect', this.connect.bind(this));
    this.socket.on('disconnect', this.disconnect.bind(this));
    this.socket.on('welcome', this.welcome.bind(this));
  }

 connect() {
    this.setState({status: 'connected'});
 }

 disconnect() {
    this.setState({status: 'disconnected'});
 }

 welcome(serverState) {
    this.setState({title: serverState.title});
 }

render() {
   return (
     <div>
       <Header title={ this.state.title } status={ this.state.status }/>
       { /* I WANT TO PASS THIS.STATE.STATUS TO CHILD ROUTES  */}
       <Routes history={ history } />
     </div>
    );
  }
}

Routes.js

import React, { Component } from 'react';
import Route from 'react-router';
import APP from '../components/APP';
import Audience from '../components/Audience';
import Board from '../components/Board';
import Speaker from '../components/Speaker';
import NotFound from '../components/NotFound';


export default class Routes extends Component {
  constructor() {
    super();
  }

  render() {
    return (
      <Route history={ this.props.history } component={ APP }>
        <Route path="/" component={ Audience } />
        <Route path="audience" component={ Audience } />
        <Route path="board" component={ Board } />
        <Route path="speaker" component={ Speaker } />
        <Route path="*" component={ NotFound } />
      </Route>
    );
  }
}

Audience.js

import React, { Component } from 'react';

export default class Audience extends Component {

 constructor() {
   super();
 }


 render() {
   return (
     <div>
      Audience - STUCK HERE!! - How to pass APP's this.state.status as a prop????        
     </div>
   );
 }

}

Although the app runs, and I have read the documentation, I am still unable to pass APP's this.state.status as a property to the Audience app.

I have been at this for 2 days to no avail and it is becoming frustrating. TGIF.

Desired Result:

When a browser is opened to localhost:3000, the default page (Audience.js), should read as:

 Untitled Presentation - connected

 Audience - connected

I cannot get the status of connected passed to the Audience component so the word 'connected' is not showing up next to Audience. I am connected as evidenced by Header's "Untitled Presentation - connected"

Can someone assist me here.

Many Thanks!

Upvotes: 0

Views: 543

Answers (2)

user1177440
user1177440

Reputation:

SOLUTION:

As Clarkie mentioned, I did have cyclic dependancy because I was following a legacy setup which used react-router 0.13 and had APP as the entry point.

Much of the help for this problem came from iam4x/isomorphic-flux-boilerplate

It is 'sad' detailed assistance could not have been found directly from the react-router documentation.


My new entry point is now:

Index.js

import React from 'react';
import ReactDOM from 'react-dom';
import Router from 'react-router';
import createBrowserHistory from 'history/lib/createBrowserHistory';

  const routerProps = {
    routes: require('./router/routes'),
    history: createBrowserHistory(),
    createElement: (component, props) => {
      return React.createElement(component, { ...props});
    }
  };

  ReactDOM.render(
    React.createElement(Router, { ...routerProps }),
    document.getElementById('root')
  );

Routes.js:

Note: I especially like how they did the routes, because I can see quite clearly how to turn this dynamic (w/ data from Db) for large apps.

import React from 'react';
import { Route } from 'react-router';
import { generateRoute } from '../utils/localized-routes';

export default (
  <Route component={ require('../components/APP') }>
    { generateRoute({
      paths: ['/', '/audience'],
      component: require('../components/Audience')
    }) }
    { generateRoute({
      paths: ['/speaker'],
      component: require('../components/Speaker')
    }) }
    { generateRoute({
      paths: ['board'],
      component: require('../components/Board')
    }) }
    <Route path="*" component={ require('../components/NotFound') } />
  </Route>
);

localized-routes.js:

import React from 'react';
import { Route } from 'react-router';

export function generateRoute({ paths, component }) {
  return paths.map(function(path) {
    const props = { key: path, path, component };
    // Static `onEnter` is defined on
    // component, we should pass it to route props
    if (component.onEnter) props.onEnter = component.onEnter;
    return <Route {...props} />;
  });
}

// SWEET!!! Nice touch.
export function replaceParams(route, params) {
  let parsedRoute = route.trim();
  Object.keys(params).forEach(function(paramKey) {
    const param = ':' + paramKey;
    const paramValue = params[paramKey];
    if (parsedRoute && parsedRoute.match(param)) {
      parsedRoute = parsedRoute.replace(param, paramValue);
    }
  });
  return parsedRoute;
}

APP.js:

import React, { Component, PropTypes } from 'react';
import io from 'socket.io-client';
import Header from './parts/Header';

export default class APP extends Component {
  static propTypes = {
    children: PropTypes.element
  }

  constructor(props, context) {
   super(props, context);
   this.state = ({
    status: 'disconnected',
    title: ''
  });
 }

 componentWillMount() {
  this.socket = io('http://localhost:3000');
  this.socket.on('connect', this.connect.bind(this));
  this.socket.on('disconnect', this.disconnect.bind(this));
  this.socket.on('welcome', this.welcome.bind(this));
}

 connect() {
   this.setState({ status: 'connected' });
}

 disconnect() {
   this.setState({ status: 'disconnected' });
}

 welcome(serverState) {
   this.setState({ title: serverState.title });
}

 renderChild = () =>
   React.cloneElement(this.props.children, { status: this.state.status });

 render() {
   return (
      <div>
         <Header title={ this.state.title } status={ this.state.status }/>
        { React.Children.map(this.props.children, this.renderChild) }
      </div>
    );
  }
}

Audience.js:

import React, { Component } from 'react';
import Display from './Display';

export default class Audience extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div>
       Audience - { this.props.status }
        <Display if={ this.props.status === 'connected' }>
          <h1>Join the session</h1>
        </Display>
      </div>
     );
   }
 }

Display.js:

import React, { Component } from 'react';

export default class Display extends Component {
  render() {
    return (
        <div>
        { this.props.if ? <div> { this.props.children } </div> : null }
        </div>
        );
    }
}

DESIRED RESULT:

enter image description here

Upvotes: 0

Clarkie
Clarkie

Reputation: 7550

In your APP component you need to include the following:

{React.cloneElement(this.props.children, {status: this.state.status })}

Then in your audience component you'll have that available as this.props.status.

Edit:

I've just noticed that you have a cyclic dependency. I'd recommend getting rid of that so that the dependency is only in one direction:

routes.js --> app.js --> audience.js

Have a look at this example. This could be broken up into three files by extracting the two React classes:

  1. main.js this renders the routes
  2. App.js this renders the app and includes the child routes
  3. Taco.js this renders the taco.

This can then be represented as follows:

    main.js --> App.js --> Taco.js

Upvotes: 1

Related Questions