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

15

95

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?

Alexandra answered 21/9, 2018 at 16:30 Comment(4)
I would recommend checking out react-helmet, it makes this sort of thing really easyCocksure
are you using connected-react-router?Zita
@Sagivb.g Yes, I am using connected-react-router.Alexandra
@Sawtaytoes, check my answer below. It uses react-router with one wrapper component, without redundant code.Apraxia
J
3

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.

Jessy answered 14/3, 2023 at 15:22 Comment(0)
A
119

<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>
  )}
/>
Apraxia answered 9/1, 2019 at 14:50 Comment(8)
This should be the accepted answer, this is much more effective and reduces the need for boilerplate code.Hurtless
@Hurtless You may a simpler solution, but this one is useful yet.Abbott
you could improve this answer with an example with hooks: useEffect(() => { document.title = title; }, []) personnally i use a custom hook if the title depends on props ` import { isFunction } from 'lodash'; import { useEffect } from 'react'; export default function useTitle(titleOrFn, ...deps) { useEffect(() => { document.title = isFunction(titleOrFn) ? titleOrFn() : titleOrFn; }, [...deps]); }; ` then simply useTitle(()=> 'Profile of ' + userId, [userId])Wallack
@Wallack please, share the code on jsfiddle or some coding resourceApraxia
Note: This is for react-router >= 4.x. Tried it on 3.x and it didn't work because it doesn't have a render prop so I had to set up a bit of a weird workaround/hack.Osugi
props.title should be in the dependency array for the effect since the title should be updated again if the title passed in through the props changes: ``` useEffect(() => { document.title = props.title || ""; }, [props.title]); ```Take
I found that you can simply at title as a <Route> like so: <Route path='/' title='Home' exact component={Home} /> You then can access the title via props and set it. This eliminates the need for the <Page> wrapper.Custom
@Custom see differences between using component and render propApraxia
L
69

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.

Laryngo answered 21/9, 2018 at 16:53 Comment(3)
I am going with react-helmet, but the other solution would work as well.Alexandra
This does not answer the question, even using helmet in componentDidMount() is not efficient. Is there a way to do this via the router, that is the question.Schwitzer
@Schwitzer It does answer the question, that is why it is the accepted answer. Regarding your query you can use react-router life cycles hook to do the same.Laryngo
E
33

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]);
};
Effendi answered 29/3, 2019 at 10:46 Comment(8)
React recommends composition instead of inheritance, so I wouldn't recommend this. See: reactjs.org/docs/composition-vs-inheritance.htmlNuclear
I prefer this answer but too sad it's not the "recommended way"Wallack
Changed to use composition as well as hooks. Happy codingEffendi
one small thing - did you mean Page instead of Route in the improvement block? probably just a typoOnestep
@jelle they do recommend against inheritance, BUT, as far as I'm aware this is to prevent people from their tendency to using sub-optimal patterns that are familiar already. I'm not aware of any actual risk or negative to using this strategy in rare cases. It can be exceptionally helpful, but it should be a last resort. To give some context, I've used it myself in one place in a thousand file project, to emphasize how rarely you should need to reach for this. If there are any real downsides to using inheritance please correct me.Serna
Current solution does not use any inheritance.Effendi
@ThierryProst I suggest you update the first phrase of your answer to your liking, since it doesn't use inheritance anymore.Gerena
I would oppose this approach and go with @Apraxia since starting from react-router@6, you cannot have a Route component outside the Routes component. That being said, the only way you can achieve this thing in the modern router version is by actually creating our separate Page or Guard component where we put all of our authentications, navigation guards, and all this extra logic in there.Strap
R
15

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>
    );
}
Rameses answered 30/5, 2019 at 21:52 Comment(3)
This works well for me, however it's window.location.pathname I also sliced of the slash and added a default because the home route would be blank.Empathize
Good solution with less code. I used the useLocation hook and location.pathname instead of history.location.pathname. See @Tolumide answer https://mcmap.net/q/222599/-is-there-a-way-to-modify-the-page-title-with-react-router-v4 below.Ellipsis
@Antony yes, I agree. useLocation hook would be better :)Rameses
G
14

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.

Goer answered 29/3, 2019 at 12:50 Comment(2)
This is a great solution. Do you have a TypeScript version of your PrivateRoute component?Sarcoma
@Sarcoma I have added my PrivateRoute component in it's current TypeScript format. It's at the bottom of my post. I hope it helps.Goer
P
5

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>
  )
}
Pirali answered 17/5, 2019 at 15:33 Comment(0)
W
5

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

Wallack answered 17/5, 2019 at 15:52 Comment(0)
F
4

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.

Ferrer answered 24/10, 2020 at 1:9 Comment(1)
I did not do much pathname processing, so the useEffect was simpler : ` useEffect(() => { document.title = Grade | ${location.pathname.replace('/', '')}; }, [location]); `Ellipsis
S
3

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)

Stephine answered 6/2, 2020 at 15:40 Comment(0)
J
3

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.

Jessy answered 14/3, 2023 at 15:22 Comment(0)
S
2

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

Spoonbill answered 18/4, 2020 at 13:10 Comment(1)
This was the easiest approach I think. Thanks.Doggone
S
1

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'>
Salvidor answered 17/5, 2020 at 17:22 Comment(0)
T
0

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

useEffect(() =>
{
    document.title = "My page title";
}, []);
Thermometer answered 20/9, 2023 at 13:18 Comment(0)
T
0

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')
Tony answered 21/1 at 2:45 Comment(0)
S
0

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;
Sampling answered 12/4 at 18:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.