Skip to content

Commit

Permalink
Add test and README for useHelmet() hook
Browse files Browse the repository at this point in the history
  • Loading branch information
kouhin committed May 21, 2019
1 parent 21bcd0e commit 77cf9d9
Show file tree
Hide file tree
Showing 6 changed files with 5,169 additions and 45 deletions.
15 changes: 1 addition & 14 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,6 @@
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "error",
"prettier/prettier": [
"error",
{
"printWidth": 80,
"tabWidth": 4,
"singleQuote": false,
"trailingComma": "none",
"bracketSpacing": false,
"semi": true,
"useTabs": false,
"parser": "babel",
"jsxBracketSameLine": false
}
]
"prettier/prettier": "error"
}
}
11 changes: 11 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"printWidth": 80,
"tabWidth": 4,
"singleQuote": false,
"trailingComma": "none",
"bracketSpacing": false,
"semi": true,
"useTabs": false,
"parser": "babel",
"jsxBracketSameLine": false
}
175 changes: 155 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,8 @@ Helmet _takes_ plain HTML tags and _outputs_ plain HTML tags. It's dead simple,
*This is a fork of [react-helmet](https://github.com/nfl/react-helmet).*

## Example
```javascript
import React from "react";
import {Helmet} from "react-safety-helmet";

class Application extends React.Component {
render () {
return (
<div className="application">
<Helmet>
<meta charSet="utf-8" />
<title>My Title</title>
<link rel="canonical" href="http://mysite.com/example" />
</Helmet>
...
</div>
);
}
};
```

or with react hooks
### With React Hooks API `useHelmet(props)`

``` javascript
import React from "react";
Expand All @@ -63,6 +44,28 @@ function Application() {
};
```

### With Declarative API `<Helmet props>`

```javascript
import React from "react";
import {Helmet} from "react-safety-helmet";

class Application extends React.Component {
render () {
return (
<div className="application">
<Helmet>
<meta charSet="utf-8" />
<title>My Title</title>
<link rel="canonical" href="http://mysite.com/example" />
</Helmet>
...
</div>
);
}
};
```

Nested or latter components will override duplicate changes:

```javascript
Expand Down Expand Up @@ -221,6 +224,138 @@ new Promise((resolve, reject) => {

## Reference Guide

### React Hooks API `useHelmet(props)`

``` javascript
useHelmet({
/* (optional) set to false to disable string encoding (server-only) */
encodeSpecialCharacters: true,
/*
(optional) Useful when you want titles to inherit from a template:
useHelmet({
titleTemplate: "%s | MyAwesomeWebsite.com",
title: "My Title"
});
outputs:
<head>
<title>Nested Title | MyAwesomeWebsite.com</title>
</head>
*/,
titleTemplate: "MySite.com - %s",
/*
(optional) used as a fallback when a template exists but a title is not defined
useHelmet({
defaultTitle: "My Site",
titleTemplate: "My Site - %s"
});
outputs:
<head>
<title>My Site</title>
</head>
*/
defaultTitle: "My Default Title",
/* (optional) callback that tracks DOM changes */
onChangeClientState: (newState) => console.log(newState),
/* html attributes */
htmlAttributes: {
lang: "en"
amp: true,
},
/* body attributes */
bodyAttributes: {
className: "root"
},
/* title attributes and value */
title: `My Plain Title or ${dynamic} title`,
titleAttributes: {
itemProp: "name",
lang: "en",
},
/* base element */
base: {
target: "_blank",
href: "http://mysite.com/"
},
/* multiple meta elements */
meta: [
{
name: "description",
content: "Helmet application"
},
{
property: "og:type",
content: "article"
}
],
/* multiple link elements */
link: [
{
rel: "canonical",
href: "http://mysite.com/example"
},
{
rel: "apple-touch-icon",
href: "http://mysite.com/img/apple-touch-icon-57x57.png"
},
{
rel: "apple-touch-icon"
sizes: "72x72"
href: "http://mysite.com/img/apple-touch-icon-72x72.png"
},
...locales.map((locale) => ({
rel: "alternate",
href: `http://example.com/${locale}`
hrefLang: locale
}))
],
/* multiple script elements */
script: [
{
src: "http://include.com/pathtojs.js",
type: "text/javascript"
}
/* inline script elements */
{
type: "application/ld+json"
innerHTML: `
{
"@context": "http://schema.org"
}
`
}
],
noscript: [
/* noscript elements */
{
innerHTML: `
<link rel="stylesheet" type="text/css" href="foo.css" />
`
}
],
/* inline style elements */
style: [
{
innerHTML: `
body {
background-color: blue;
}
p {
font-size: 12px;
}
`
}
]
});
```

### Declarative API `<Helmet props>`

```javascript
<Helmet
{/* (optional) set to false to disable string encoding (server-only) */}
Expand Down
30 changes: 20 additions & 10 deletions src/Helmet.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const {
useRef
} = React;

const useIsomorphicEffect = ExecutionEnvironment.canUseDOM
? useLayoutEffect
: useEffect;

const HelmetContext = createContext();
HelmetContext.displayName = "HelmetContext";

Expand Down Expand Up @@ -60,14 +64,21 @@ function HelmetProvider({
children,
store = createHelmetStore()
}) {
const prevReducedState = useRef(null);
const dispatch = useCallback(
action => {
const nextState = rootReducer(store.state, action);
if (store.setState(nextState, action)) {
if (canUseDOM) {
handleClientStateChange(
reducePropsToState(store.state.propsList)
const nextReducedState = reducePropsToState(
store.state.propsList
);
if (
!deepEqual(prevReducedState.current, nextReducedState)
) {
prevReducedState.current = nextReducedState;
handleClientStateChange(nextReducedState);
}
}
}
return nextState;
Expand Down Expand Up @@ -276,7 +287,7 @@ function generateUniqueString() {
);
}

function useHelmet(props) {
function useHelmet(props = {}) {
const instance = useMemo(() => generateUniqueString(), []);
const dispatch = useContext(HelmetContext);
const called = useRef(false);
Expand All @@ -287,13 +298,17 @@ function useHelmet(props) {
}
prevProps.current = props;
const {children, ...restProps} = props;
let newProps = {...restProps};
let newProps = {
defer: true,
encodeSpecialCharacters: true,
...restProps
};
if (children) {
newProps = mapChildrenToProps(children, newProps);
}
dispatch(addProps(instance, newProps));
}, [dispatch, instance, props]);
useLayoutEffect(() => {
useIsomorphicEffect(() => {
// componentDidMount, componentDidUpdate
if (called.current) {
sideEffect();
Expand All @@ -317,11 +332,6 @@ const Helmet = props => {

Helmet.displayName = "Helmet";

Helmet.defaultProps = {
defer: true,
encodeSpecialCharacters: true
};

if (process.env.NODE_ENV !== "production") {
/**
* @param {Object} base: {"target": "_blank", "href": "http://mysite.com/"}
Expand Down
Loading

0 comments on commit 77cf9d9

Please sign in to comment.