diff --git a/package.json b/package.json index 110e1976..18bdbe5d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-safety-helmet", "description": "A fork of react-helmet that support for renderToNodeStream and thread safe, provides both react hooks and declarative api", - "version": "6.3.0", + "version": "7.0.0-beta.1", "main": "./lib/Helmet.js", "module": "es/Helmet.js", "sideEffect": false, @@ -16,8 +16,8 @@ "url": "https://github.com/openameba/react-safety-helmet" }, "keywords": [ - "react-helmet", - "nfl", + "react", + "helmet", "react", "hook", "document", @@ -47,6 +47,7 @@ "@babel/plugin-external-helpers": "^7.2.0", "@babel/plugin-proposal-class-properties": "^7.4.4", "@babel/plugin-proposal-object-rest-spread": "^7.4.4", + "@babel/plugin-proposal-private-methods": "^7.4.4", "@babel/plugin-transform-strict-mode": "^7.2.0", "@babel/preset-env": "^7.4.4", "@babel/preset-react": "^7.0.0", diff --git a/src/Helmet.js b/src/Helmet.js index 9a3ce6d2..dd074608 100644 --- a/src/Helmet.js +++ b/src/Helmet.js @@ -30,63 +30,86 @@ const useIsomorphicEffect = ExecutionEnvironment.canUseDOM const HelmetContext = createContext(); HelmetContext.displayName = "HelmetContext"; -const createHelmetStore = subscribe => { - const store = { - state: rootReducer() - }; - store.peek = () => { +class HelmetStore { + #state; + #peekCache; + #canUseDOM; + #subscribe; + #prevReducedState; + + constructor(opts = {}) { + this.#state = rootReducer(); + this.#peekCache = null; + this.#prevReducedState = null; + this.#subscribe = opts.subscribe; + this.#canUseDOM = opts.canUseDOM || ExecutionEnvironment.canUseDOM; + } + + peek = () => { if ( - !store.peekCache || - !store.peekCache.key || - store.peekCache.key !== store.state + !this.#peekCache || + !this.#peekCache.key || + this.#peekCache.key !== this.#state ) { - store.peekCache = { - key: store.state, - value: reducePropsToState(store.state.propsList) + this.#peekCache = { + key: this.#state, + value: reducePropsToState(this.#state.propsList) }; } - return store.peekCache.value; + return this.#peekCache.value; + }; + + renderStatic = () => { + return mapStateOnServer(this.peek()); }; - store.renderStatic = () => mapStateOnServer(store.peek()); - store.setState = (state, action) => { - if (state !== store.state) { - store.state = state; - if (subscribe) subscribe(action); + + setState = (state, action) => { + if (state !== this.#state) { + this.#state = state; + if (this.#subscribe) this.#subscribe(action); return true; } return false; }; - return store; + + getState = () => { + return this.#state; + }; + + dispatch = action => { + const nextState = rootReducer(this.#state, action); + if (this.setState(nextState, action)) { + if (this.#canUseDOM) { + const nextReducedState = reducePropsToState( + this.#state.propsList + ); + if (!deepEqual(this.#prevReducedState, nextReducedState)) { + this.#prevReducedState = nextReducedState; + handleClientStateChange(nextReducedState); + } + } + } + return nextState; + }; +} + +const createHelmetStore = subscribe => { + return new HelmetStore({ + subscribe + }); }; function HelmetProvider({ canUseDOM = ExecutionEnvironment.canUseDOM, children, - store = createHelmetStore() + store }) { - const prevReducedState = useRef(null); - const dispatch = useCallback( - action => { - const nextState = rootReducer(store.state, action); - if (store.setState(nextState, action)) { - if (canUseDOM) { - const nextReducedState = reducePropsToState( - store.state.propsList - ); - if ( - !deepEqual(prevReducedState.current, nextReducedState) - ) { - prevReducedState.current = nextReducedState; - handleClientStateChange(nextReducedState); - } - } - } - return nextState; - }, - [canUseDOM, store] - ); + const helmetStore = useMemo(() => store || createHelmetStore({canUseDOM}), [ + store, + canUseDOM + ]); return ( - + {children} ); @@ -97,9 +120,11 @@ if (process.env.NODE_ENV !== "production") { canUseDOM: PropTypes.bool, children: PropTypes.node, store: PropTypes.shape({ - current: PropTypes.object, + dispatch: PropTypes.func, + getState: PropTypes.func, peek: PropTypes.func, renderStatic: PropTypes.func, + setState: PropTypes.func, subscribe: PropTypes.func }) }; @@ -290,6 +315,11 @@ function generateUniqueString() { function useHelmet(props = {}) { const instance = useMemo(() => generateUniqueString(), []); const dispatch = useContext(HelmetContext); + if (!dispatch) { + throw new Error( + "You should not use useHelmet() or outside a ." + ); + } const called = useRef(false); const prevProps = useRef(); const sideEffect = useCallback(() => { diff --git a/yarn.lock b/yarn.lock index 88d8f0a3..f1188e5e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -311,6 +311,14 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" +"@babel/plugin-proposal-private-methods@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.4.4.tgz#307b7db29d8ae2d259e7c0e6e665b1922d7ac856" + integrity sha512-EIV4fDVP3XwdizJ/H6308Km+d8xdLAUCAvsY8mjxhat9I3vNgssGhZuhgn/jw7IK5/91sN8PHtVGxMjeTSrSng== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.4.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz#501ffd9826c0b91da22690720722ac7cb1ca9c78"