Kevin Ghadyani
Kevin Ghadyani

Reputation: 7307

Is there a way to modify the page title with React-Router v4+?

I'm looking for a way to modify the page title when React-Router v4+ changes locations. I used to listen for a location change action in Redux and check that route against a metaData object.

When using React-Router v4+, there's no fixed routes list. In fact, various components around the site could use Route with the same path string. That means old method I used won't work anymore.

Is there a way I can update the page title by calling actions when certain major routes are changed or is there a better a better method to update the site's metadata?

Upvotes: 97

Views: 117261

Answers (15)

nmax
nmax

Reputation: 1111

Same as Thierry Prost's custom hook, but modified for TypeScript:

import { useEffect } from "react";
const useTitle = (title:string) => {
  useEffect(() => {
    const prevTitle = document.title;
    title && (document.title = title);
    return () => {document.title = prevTitle};
  }, [title]);
};
export default useTitle;

Upvotes: 0

Si Thu
Si Thu

Reputation: 1390

I have a short and simple solution using react hook with or without react router.

I just created a file named 'useDocumentTitle.tsx' :

import React, { useEffect } from 'react';

const useDocumentTitle = (title: string, default_title: string | undefined 
= 'My default title') => {
  useEffect(() => {
    document.title = title + ' | ' + default_title;
    return () => {
      document.title = default_title;
    };
  }, []);
};

export default useDocumentTitle;

The useEffect sets the document title on page load, while the return function reset the title back to default in case the next component doesn't use the useDocumentTitle hook.

If I want to dynamically change the document title based on change in parameter, I will add the title in the dependency array of useEffect.

useEffect(() => {
    document.title = title + ' | ' + default_title;
    return () => {
        document.title = default_title;
    };
}, [title]);

In my components, I just add :

useDocumentTitle('Customer')

If I want to change the title to different default title on leaving page :

useDocumentTitle('Customer', 'My Company Info')

Upvotes: 1

Auguste
Auguste

Reputation: 2201

If you just want to show the title of the component, the code example below should also work:

useEffect(() =>
{
    document.title = "My page title";
}, []);

Upvotes: 0

Denis Borisov
Denis Borisov

Reputation: 304

I asked question "how to set title correctly" on official github repo of "react router dom". They answered "This isn't something the Router does. You're looking for a library like react-helmet.".

If you want to change title in react router tutorial, you just need to

npm install react-helmet

after that go to your react router dom page (eg contact.jsx) and add such code

import { Helmet } from "react-helmet";

export default function Contact() {
  const { contact } = useLoaderData();
  return (
    <div id="contact">

      <Helmet>
        <title>{ contact.first??'Новый контакт' }</title>
      </Helmet>

And it works correctly in march 2023.

Upvotes: 3

Tolumide
Tolumide

Reputation: 994

I am answering this because I feel you could go an extra step to avoid repetitions within your components and you could just get the title updated from one place (the router's module).

I usually declare my routes as an array but you could change your implementation depending on your style. so basically something like this ==>

import {useLocation} from "react-router-dom";
const allRoutes = [
  {
        path: "/talkers",
        component: <Talkers />,
        type: "welcome",
        exact: true,
    },
    {
        path: "/signup",
        component: <SignupPage />,
        type: "onboarding",
        exact: true,
    },
  ]

const appRouter = () => {
    const theLocation = useLocation();
    const currentLocation = theLocation.pathname.split("/")[1];
    React.useEffect(() => {
        document.title = `<Website Name> | 
        ${currentLocation[0].toUpperCase()}${currentLocation.slice(1,)}`
    }, [currentLocation])

   return (
     <Switch>
      {allRoutes.map((route, index) => 
        <Route key={route.key} path={route.path} exact={route.exact} />}
    </Switch>

   )

}

Another approach would be declaring the title already in each of the allRoutes object and having something like @Denis Skiba's solution here.

Upvotes: 4

Thierry Prost
Thierry Prost

Reputation: 1025

Picking up from the excellent answer of phen0menon, why not extend Route instead of React.Component?

import React, { useEffect } from 'react';
import { Route } from 'react-router-dom';
import PropTypes from 'prop-types';

export const Page = ({ title, ...rest }) => {
  useEffect(() => {
    document.title = title;
  }, [title]);
  return <Route {...rest} />;
};

This will remove overhead code as seen below:

// old:
  <Route
    exact
    path="/"
    render={props => (
      <Page {...props} component={Index} title="Index Page" />
    )}
  />

// improvement:
  <Page
    exact
    path="/"
    component={Index}
    title="Index Page"
  />

Update: another way to do it is with a custom hook:

import { useEffect } from 'react';

/** Hook for changing title */
export const useTitle = title => {
  useEffect(() => {
    const oldTitle = document.title;
    title && (document.title = title);
    // following line is optional, but will reset title when component unmounts
    return () => document.title = oldTitle;
  }, [title]);
};

Upvotes: 35

Hsjakobsen
Hsjakobsen

Reputation: 346

I built a bit on Thierry Prosts solution and ended up with the following:

UPDATE January 2020: I've now updated my component to be in Typescript as well:

UPDATE August 2021: I've added my private route in TypeScript

import React, { FunctionComponent, useEffect } from 'react';
import { Route, RouteProps } from 'react-router-dom';

interface IPageProps extends RouteProps {
  title: string;
}

const Page: FunctionComponent<IPageProps> = props => {
  useEffect(() => {
    document.title = "Website name | " + props.title;
  });

  const { title, ...rest } = props;
  return <Route {...rest} />;
};

export default Page;

UPDATE: My Page.jsx component is now a functional component and with useEffect hook:

import React, { useEffect } from 'react';
import { Route } from 'react-router-dom';

const Page = (props) => {
  useEffect(() => {    
    document.title = "Website name | " + props.title;
  });

  const { title, ...rest } = props;
  return <Route {...rest} />;
}

export default Page;

Below you can find my initial solution:

// Page.jsx
import React from 'react';
import { Route } from 'react-router-dom';

class Page extends Route {
  componentDidMount() {
    document.title = "Website name | " + this.props.title;
  }

  componentDidUpdate() {      
      document.title = "Website name | " + this.props.title;
  }

  render() {
    const { title, ...rest } = this.props;
    return <Route {...rest} />;
  }
}

export default Page;

And my Router implementation looked like this:

// App.js / Index.js
<Router>
    <App>
      <Switch>
         <Page path="/" component={Index} title="Index" />
         <PrivateRoute path="/secure" component={SecurePage} title="Secure" />
      </Switch>
    </App>    
  </Router>

Private route setup:

// PrivateRoute
function PrivateRoute({ component: Component, ...rest }) {
  return (
    <Page
      {...rest}
      render={props =>
        isAuthenticated ? (
          <Component {...props} />
        ) : (
          <Redirect
            to={{
              pathname: "/",
              state: { from: props.location }
            }}
          />
        )
      }
    />
  );
}

Private Route in TypeScript:

export const PrivateRoute = ({ Component, ...rest }: IRouteProps): JSX.Element => {
  return (
    <Page
      {...rest}
      render={(props) =>
        userIsAuthenticated ? (
          <Component {...props} />
        ) : (
          <Redirect
            to={{
              pathname: Paths.login,
              state: { from: props.location },
            }}
          />
        )
      }
    />
  );
};

This enabled me to have both public areas update with a new title and private areas also update.

Upvotes: 14

phen0menon
phen0menon

Reputation: 2462

<Route /> components have render property. So you can modify the page title when location changes by declaring your routes like that:

<Route
  exact
  path="/"
  render={props => (
    <Page {...props} component={Index} title="Index Page" />
  )}
/>

<Route
  path="/about"
  render={props => (
    <Page {...props} component={About} title="About Page" />
  )}
/>

In Page component you can set the route title:

import React from "react"

/* 
 * Component which serves the purpose of a "root route component". 
 */
class Page extends React.Component {
  /**
   * Here, we define a react lifecycle method that gets executed each time 
   * our component is mounted to the DOM, which is exactly what we want in this case
   */
  componentDidMount() {
    document.title = this.props.title
  }
  
  /**
   * Here, we use a component prop to render 
   * a component, as specified in route configuration
   */
  render() {
    const PageComponent = this.props.component

    return (
      <PageComponent />
    )
  }
}

export default Page

Update 1 Aug 2019. This only works with react-router >= 4.x. Thanks to @supremebeing7

Updated answer using React Hooks:

You can specify the title of any route using the component below, which is built by using useEffect.

import { useEffect } from "react";

const Page = (props) => {
  useEffect(() => {
    document.title = props.title || "";
  }, [props.title]);
  return props.children;
};

export default Page;

And then use Page in the render prop of a route:

<Route
  path="/about"
  render={(props) => (
    <Page title="Index">
      <Index {...props} />
    </Page>
  )}
/>

<Route
  path="/profile"
  render={(props) => (
    <Page title="Profile">
      <Profile {...props} />
    </Page>
  )}
/>

Upvotes: 124

machineghost
machineghost

Reputation: 35725

Dan Abramov (creator of Redux and current member of the React team) created a component for setting the title which works with new versions of React Router also. It's super easy to use and you can read about it here:

https://github.com/gaearon/react-document-title

For instance:

<DocumentTitle title='My Web App'>

Upvotes: 1

Mr.X
Mr.X

Reputation: 31345

Please use react-helmet. I wanted to give the Typescript example:

import { Helmet } from 'react-helmet';

const Component1Title = 'All possible elements of the <head> can be changed using Helmet!';
const Component1Description = 'No only title, description etc. too!';

class Component1 extends React.Component<Component1Props, Component1State> {
  render () {
    return (
      <>
        <Helmet>
          <title>{ Component1Title }</title>
          <meta name="description" content={Component1Description} />

        </Helmet>
        ...
      </>
    )
  }
}

Learn more: https://github.com/nfl/react-helmet#readme

Upvotes: 2

Denis Skiba
Denis Skiba

Reputation: 31

You also can go with the render method

const routes = [
 {
   path: "/main",
   component: MainPage,
   title: "Main Page",
   exact: true
 },
 {
   path: "/about",
   component: AboutPage,
   title: "About Page"
 },
 {
   path: "/titlessPage",
   component: TitlessPage
 }
];

const Routes = props => {
 return routes.map((route, idx) => {
   const { path, exact, component, title } = route;
   return (
     <Route
       path={path}
       exact={exact}
       render={() => {
         document.title = title ? title : "Unknown title";
         console.log(document.title);
         return route.component;
       }}
     />
   );
 });
};

the example at codesandbox (Open result in a new window for see title)

Upvotes: 3

Jordan Daniels
Jordan Daniels

Reputation: 5304

Using a functional component on your main routing page, you can have the title change on each route change with useEffect.

For example,

const Routes = () => {
    useEffect(() => {
      let title = history.location.pathname
      document.title = title;
    });

    return (
      <Switch>
        <Route path='/a' />
        <Route path='/b' />
        <Route path='/c' />
      </Switch>
    );
}

Upvotes: 15

TecHunter
TecHunter

Reputation: 6141

Here is my solution which is almost the same as simply setting document.title but using useEffect

/**
* Update the document title with provided string
 * @param titleOrFn can be a String or a function.
 * @param deps? if provided, the title will be updated when one of these values changes
 */
function useTitle(titleOrFn, ...deps) {
  useEffect(
    () => {
      document.title = isFunction(titleOrFn) ? titleOrFn() : titleOrFn;
    },
    [...deps]
  );
}

This has the advantage to only rerender if your provided deps change. Never rerender:

const Home = () => {
  useTitle('Home');
  return (
    <div>
      <h1>Home</h1>
      <p>This is the Home Page</p> 
    </div>
  );
}

Rerender only if my userId changes:

const UserProfile = ({ match }) => {
  const userId = match.params.userId;
  useTitle(() => `Profile of ${userId}`, [userId]);
  return (
    <div>
      <h1>User page</h1>
      <p>
        This is the user page of user <span>{userId}</span>
      </p>
    </div>
  );
};

// ... in route definitions
<Route path="/user/:userId" component={UserProfile} />
// ...

CodePen here but cannot update frame title

If you inspect the <head> of the frame you can see the change: screenshot

Upvotes: 5

Fellow Stranger
Fellow Stranger

Reputation: 34113

With a little help from Helmet:

import React from 'react'
import Helmet from 'react-helmet'
import { Route, BrowserRouter, Switch } from 'react-router-dom'

function RouteWithTitle({ title, ...props }) {
  return (
    <>
      <Helmet>
        <title>{title}</title>
      </Helmet>
      <Route {...props} />
    </>
  )
}

export default function Routing() {
  return (
    <BrowserRouter>
      <Switch>
        <RouteWithTitle title="Hello world" exact={true} path="/" component={Home} />
      </Switch>
    </BrowserRouter>
  )
}

Upvotes: 5

Adeel Imran
Adeel Imran

Reputation: 13986

In your componentDidMount() method do this for every page

componentDidMount() {
  document.title = 'Your page title here';
}

This will change your page title, do the above mentioned for every route.

Also if it is more then just the title part, check react-helmet It is a very neat library for this, and handles some nice edge cases as well.

Upvotes: 69

Related Questions