Last active
October 24, 2016 06:25
-
-
Save frenzzy/3c09b527c587f0b2dd232195bc591bc7 to your computer and use it in GitHub Desktop.
React-Hot-Loader deepForceUpdate example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* React Starter Kit (https://www.reactstarterkit.com/) | |
* | |
* Copyright © 2014-2016 Kriasoft, LLC. All rights reserved. | |
* | |
* This source code is licensed under the MIT license found in the | |
* LICENSE.txt file in the root directory of this source tree. | |
*/ | |
import 'babel-polyfill'; | |
import React from 'react'; | |
import ReactDOM from 'react-dom'; | |
import FastClick from 'fastclick'; | |
import UniversalRouter from 'universal-router'; | |
import queryString from 'query-string'; | |
import { AppContainer } from 'react-hot-loader'; | |
import { createPath } from 'history/PathUtils'; | |
import history from './core/history'; | |
import App from './components/App'; | |
// Global (context) variables that can be easily accessed from any React component | |
// https://facebook.github.io/react/docs/context.html | |
const context = { | |
// Enables critical path CSS rendering | |
// https://github.com/kriasoft/isomorphic-style-loader | |
insertCss: (...styles) => { | |
// eslint-disable-next-line no-underscore-dangle | |
const removeCss = styles.map(x => x._insertCss()); | |
return () => { removeCss.forEach(f => f()); }; | |
}, | |
}; | |
function updateTag(tagName, keyName, keyValue, attrName, attrValue) { | |
const node = document.head.querySelector(`${tagName}[${keyName}="${keyValue}"]`); | |
if (node && node.getAttribute(attrName) === attrValue) return; | |
// Remove and create a new tag in order to make it work with bookmarks in Safari | |
if (node) { | |
node.parentNode.removeChild(node); | |
} | |
if (typeof attrValue === 'string') { | |
const nextNode = document.createElement(tagName); | |
nextNode.setAttribute(keyName, keyValue); | |
nextNode.setAttribute(attrName, attrValue); | |
document.head.appendChild(nextNode); | |
} | |
} | |
function updateMeta(name, content) { | |
updateTag('meta', 'name', name, 'content', content); | |
} | |
function updateCustomMeta(property, content) { // eslint-disable-line no-unused-vars | |
updateTag('meta', 'property', property, 'content', content); | |
} | |
function updateLink(rel, href) { // eslint-disable-line no-unused-vars | |
updateTag('link', 'rel', rel, 'href', href); | |
} | |
// Switch off the native scroll restoration behavior and handle it manually | |
// https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration | |
const scrollPositionsHistory = {}; | |
if (window.history && 'scrollRestoration' in window.history) { | |
window.history.scrollRestoration = 'manual'; | |
} | |
let onRenderComplete = function initialRenderComplete() { | |
const elem = document.getElementById('css'); | |
if (elem) elem.parentNode.removeChild(elem); | |
onRenderComplete = function renderComplete(route, location) { | |
document.title = route.title; | |
updateMeta('description', route.description); | |
// Update necessary tags in <head> at runtime here, ie: | |
// updateMeta('keywords', route.keywords); | |
// updateCustomMeta('og:url', route.canonicalUrl); | |
// updateCustomMeta('og:image', route.imageUrl); | |
// updateLink('canonical', route.canonicalUrl); | |
// etc. | |
let scrollX = 0; | |
let scrollY = 0; | |
const pos = scrollPositionsHistory[location.key]; | |
if (pos) { | |
scrollX = pos.scrollX; | |
scrollY = pos.scrollY; | |
} else { | |
const targetHash = location.hash.substr(1); | |
if (targetHash) { | |
const target = document.getElementById(targetHash); | |
if (target) { | |
scrollY = window.pageYOffset + target.getBoundingClientRect().top; | |
} | |
} | |
} | |
// Restore the scroll position if it was saved into the state | |
// or scroll to the given #hash anchor | |
// or scroll to top of the page | |
window.scrollTo(scrollX, scrollY); | |
// Google Analytics tracking. Don't send 'pageview' event after | |
// the initial rendering, as it was already sent | |
if (window.ga) { | |
window.ga('send', 'pageview', createPath(location)); | |
} | |
}; | |
}; | |
// Make taps on links and buttons work fast on mobiles | |
FastClick.attach(document.body); | |
const container = document.getElementById('app'); | |
let currentLocation = history.location; | |
let routes = require('./routes').default; | |
// Re-render the app when window.location changes | |
async function onLocationChange(location) { | |
// Remember the latest scroll position for the previous location | |
scrollPositionsHistory[currentLocation.key] = { | |
scrollX: window.pageXOffset, | |
scrollY: window.pageYOffset, | |
}; | |
// Delete stored scroll position for next page if any | |
if (history.action === 'PUSH') { | |
delete scrollPositionsHistory[location.key]; | |
} | |
currentLocation = location; | |
try { | |
// Traverses the list of routes in the order they are defined until | |
// it finds the first route that matches provided URL path string | |
// and whose action method returns anything other than `undefined`. | |
const route = await UniversalRouter.resolve(routes, { | |
path: location.pathname, | |
query: queryString.parse(location.search), | |
}); | |
// Prevent multiple page renders during the routing process | |
if (currentLocation.key !== location.key) { | |
return; | |
} | |
if (route.redirect) { | |
history.replace(route.redirect); | |
return; | |
} | |
ReactDOM.render( | |
<AppContainer> | |
<App context={context}>{route.component}</App> | |
</AppContainer>, | |
container, | |
() => onRenderComplete(route, location) | |
); | |
} catch (err) { | |
if (process.env.NODE_ENV !== 'production') { | |
throw err; | |
} | |
// Avoid broken navigation in production mode by a full page reload on error | |
console.error(err); // eslint-disable-line no-console | |
window.location.reload(); | |
} | |
} | |
// Handle client-side navigation by using HTML5 History API | |
// For more information visit https://github.com/mjackson/history#readme | |
history.listen(onLocationChange); | |
onLocationChange(currentLocation); | |
// Enable Hot Module Replacement (HMR) | |
if (module.hot) { | |
module.hot.accept('./routes', () => { | |
routes = require('./routes').default; // eslint-disable-line global-require | |
onLocationChange(currentLocation); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment