Messing with

Document Title & React

wei / is fe / 02 20 2020

“Kaya Toast should allow users to set document title”

so I thought

document title

was easy

document.title = "kaya toast"?

import * as React from 'react';

const useTitleNaive = title => {
  React.useEffect(() => {
    document.title = title;
  }, [title]);
};

export default () => {
  useTitleNaive('hello!');
  return <>title should be "hello!"</>;
};

why u no work?

react helmet

is managing the relevant tags

problem 1

setting title is a global side effect that persists

problem #2

when multiple <Title /> components (or equivalent hooks) are mounted, it is hard to reason who wins

problem #3

import * as React from 'react';

const useTitleNaive = title => {
  React.useEffect(() => {
    document.title = title;
  }, [title]);
};

const Component = ({ title }) => {
  useTitleNaive(title);
  return <div>component sets the title to {title}</div>;
};

export default () => {
  useTitleNaive('page');
  return (
    <>
      page sets title to "page"
      <Component title="component" />
    </>
  );
};
import * as React from 'react';

const Title = () => {
  const useTitleNaive = title => {
  React.useEffect(() => {
    document.title = title;
  }, [title]);
};
}

const Component = ({ title }) => {
  return <div>
    <Title>{title}</Title>
    component sets the title to {title}</div>;
};

export default () => {
  return (
    <>
      <Title>Page</Title>
      page sets title to "page"
      <Component title="component" />
    </>
  );
};

how do other people do it?

how does

SPACE

do it

import { useState, useEffect } from 'react'
import { space, types } from '@space/core'
import { withRouter } from 'react-router-dom'

const DEFAULT_TITLE = 'Space'

function Title ({ location }) {
  const [title, setTitle] = useState(DEFAULT_TITLE)
  const { pathname } = location

  useEffect(() => {
    const menuItems = space.get(types.MENU_ITEMS)
    const newTitle = findMenuTitle(menuItems, pathname) || DEFAULT_TITLE

    document.title = (newTitle === DEFAULT_TITLE) ? newTitle : `SPACE | ${newTitle}`

    setTitle(newTitle)
  }, [pathname])

  return title
}

function findMenuTitle (menuItems, pathname) {
  for (const { items } of menuItems) {
    for (const { headerTitle, to } of items) {
      if (pathname.startsWith(to)) {
        return headerTitle
      }
    }
  }
  return null
}

export default withRouter(Title)

key reading

  • sets title on every page (so "global effect" is acceptable)
  • reads from url and finds title from page - url map

desired behavior and implementation are in play

how does

React Helmet

do it

we need to talk about React Side Effects

key concept

allows you to work on

all instances

of a component as a whole

so you can

determine who wins

when you have multiple <Title /> components

what happens under the hood

  • componentWillMount, componentDidUpdate: emitChange()
  • componentWillUnmount: remove the instance and emitChange()
  • emitChange():
    • calls reducePropsToState for you to turn relevant props to state
    • calls mapStateOnServer to execute side effects

recap

  • handles side effects from all instances of a component altogether
  • allows users to define a winning prop after iterating through all instances
  • acts in 2 phases
    • reduce props to state
    • execute side effects (client or server)
  • relies on class components' static properties

typical usages of

React Side Effects?

Dan Abramov:

React Document Title

(thanks! / just kidding)

can we create states for a class of components with hooks?

extra credit problem

back to our home-cooked document title

arguably desired behavior for Kaya Toast

> layout renders a “default” title, which individual pages and / or components can then overwrite

> leaving the page or unmounting the component “resets” the title back to a “clean state”

import * as React from 'react';
import { useLayoutContext } from '../../components/Layout/context';

type TitleProps = { children: string };

const Title = ({ children: title }: TitleProps) => {
  const { siteName } = useLayoutContext();

  React.useEffect(() => {
    document.title = title || siteName;
  }, [title]);

  return null;
};

export default Title;

what's the more intuitive API design?

utility hook?

declarativecomponent?

const MyPage = () => {
  useTitle('Kaya Toast');
  return <div />
}
const MyPage = () => {
  return <div>
    <Title>Kaya Toast</Title>
  </div>;
}

we're effectively lying, it's not rendering any tags there and it's calling a side effect hook under the hood

it feels it should stay there even after the component unmounts

we went for <Title /> because it feels more html-y than a fn call, which feels more permanent, the <Title /> mounts and unmounts with its page and such behavior is closer to our intuition

the ship has sailed, sorry

look back in this journey

- setting document title as a global side effects

- dealing with the side effects for a class of components together

- sometimes we may find easier solution because we have more assumptions about our projects

i'm sharing about

“things i learned about the React ecosystem by building without them”

in this Friday's RK, live-streamed OMG

messing with document title with react

By Wei Gao

messing with document title with react

  • 401