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
- 480