Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React router app - Route title

I'm learning React and using React-router. The app I'm building is a mobile style app with a top navigation menu, and a content section below. As I navigate the app pages I'd like to add a page 'title' to the menu bar to identify which page you are currently on.

My routes:

<Routes>
  <Route name='home' path='/' handler={HomePage}>
    <Route name='product-list' path='products/' handler={ProductList}/>
    <Route name='product-detail' path='product/:slug/' handler={ProductDetail} addHandlerKey={true}/>
  </Route>
</Routes>

HomePage.render:

<div className="container">
  <NavigationMenu />
  <div className="contents">
    {this.props.activeRouteHandler()}
  </div>
</div>

NavigationMenu.render:

<div className="navigationMenu">
  <div className="navigationMenuTitle>{this.props.title}</div>
</div>

My issue

The child routes to HomePage need to set their title based on content returned from an Ajax call.

I had thought to add callbacks to each route, passing title back to their parent class which could in turn pass this data to the NavigationMenu. Unfortunately this does not work: As you navigate through the pages, the only function that is repeatedly called is render and setting state here raises an Invariant Violation error.

My questions

  • Is there a better way of managing the title?
  • Are there any alternatives for tracking the current page than relying on the routes render function passing data to a callback (this seems dirty) each time?
like image 882
Ashley 'CptLemming' Wilson Avatar asked Oct 01 '14 00:10

Ashley 'CptLemming' Wilson


People also ask

How do you get a route name in react?

Use the useLocation() hook to get the current route with React Router, e.g. const location = useLocation() . The hook returns the current location object. For example, you can access the pathname as location.

How do I add a dynamic title to my react app?

Write the dynamic component for the title. You can write an independent component for this example, like this: // TitleComponent. jsx import React from 'react'; import Helmet from 'react-helmet'; const TitleComponent = ({ title }) => { var defaultTitle = '⚛️ app'; return ( <Helmet> <title>{title ?

What is basename BrowserRouter?

The basename prop is used to provide a base URL path for all the locations in the application. For example, if you want to render your application at the /admin path instead of rendering at the root path / , then specify the basename prop in <BrowserRouter> : <BrowserRouter basename="/admin"> <App /></BrowerRouter>


2 Answers

I was able to solve this using the React Flux pattern and re-organising my app layout.

Title component:

var Title = React.createClass({
  getInitialState: function() {
    return {
      title: Store.get()
    };
  },
  componentDidMount: function () {
    Store.addChangeListener(this._onChange);
  },
  componentWillUnmount: function() {
    Store.removeChangeListener(this._onChange);
  },
  _onChange: function() {
    this.setState({
      title: Store.get()
    });
  },
  render: function() {
    return (
      <span className="Title">{this.state.title}</span>
    );
  }
});

Page / content components follow this structure:

var Image = React.createClass({
  componentDidMount: function() {
    Action.update('Image - '+ this.props.params.service);
  },
  render: function() {
    var src = "http://place"+this.props.params.service+".com/g/400/400";
    return (
      <div className="Image">
        <img src={src}/>
      </div>
    );
  }
});

This allows components to load, setting a title dynamically and update only the title component when the app is good and ready! Nifty!

like image 173
Ashley 'CptLemming' Wilson Avatar answered Oct 04 '22 08:10

Ashley 'CptLemming' Wilson


As Jeff Fairley suggested above, using Reflux will help clean this up quite a bit (this code uses ES6 featues, so a transpiler will be needed to run it):

TitleActions

import Reflux from 'reflux';

export default Reflux.createActions([ 'update' ]); 

TitleStore

import Reflux from 'reflux';
import TitleActions from '../actions/title';

export default Reflux.createStore({
  listenables: TitleActions,

  getInitialState: function() {
    return 'My Cool New Project';
  },

  onUpdate(title) {
    this.trigger(title);
  }
});

NavigationMenu

import React from 'react/addons';
import Reflux from 'reflux';
import TitleStore from '../stores/title';

export default React.createClass({
  mixins: [ Reflux.connect(TitleStore, 'title') ],

  render() {
    return <div className="navigationMenu">
      <div className="navigationMenuTitle>{this.state.title}</div>
    </div>;
  }
});

MyComponent

import React from 'react/addons';
import Reflux from 'reflux';
import TitleActions from '../actions/title';

export default React.createClass({
  componentDidMount() {
    TitleActions.update('My Custom Title');
  },

  render() {
    return <div>My content.</div>;
  }
});

The goal is to preserve a smooth uni-directional data flow:

MyComponent -> update action -> TitleStore -> NavigationMenu
like image 40
Jan Klimo Avatar answered Oct 04 '22 09:10

Jan Klimo