Last active
May 24, 2019 18:49
-
-
Save danielpowell4/7ad0d3b646e20ce41c6e4645acbdc536 to your computer and use it in GitHub Desktop.
useServerRender React hook that fetches DOM from the server and places it (dangerously) into the view. Helpful for migrating legacy applications
This file contains 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
// I put this in lib but you do you! | |
import { useEffect, useState } from "react"; | |
import fetch from "cross-fetch"; | |
// relies on `render layout: include_layout?` | |
// at bottom of controller action | |
const fetchHTMLBody = url => | |
fetch(`${url}?layout=false`, { | |
credentials: "same-origin", | |
headers: { | |
"Content-Type": "text/html", | |
}, | |
}).then(res => res.text()); | |
export const useServerRender = ( | |
url, | |
serverSideSelector, | |
afterDOMReady = () => {} | |
) => { | |
// check if serverSideElement is initially present | |
const serverSideEl = document.querySelector(serverSideSelector); | |
// on first mount, check if serverEl there | |
// if it is _not_ there, load from server | |
const [DOMContent, setDOMContent] = useState(null); | |
useEffect(() => { | |
if (!serverSideEl) { | |
fetchHTMLBody(url).then(html => setDOMContent({ __html: html })); | |
} | |
// scrub serverSideEl onUnmount | |
return () => { | |
if (!!serverSideEl) serverSideEl.remove(); | |
}; | |
}, [serverSideEl, url]); | |
// check if desired DOM element is present | |
// when it is found (either initially or later) | |
// run provided afterDOMReady func | |
const [DOMApplied, setDOMApplied] = useState(0); | |
useEffect(() => { | |
const DOMEl = document.querySelector(serverSideSelector); | |
if (!!DOMEl && DOMApplied < 2) { | |
setDOMApplied(DOMApplied + 1); | |
} | |
// only run first time DOMEl present | |
if (DOMApplied === 1) { | |
afterDOMReady(); | |
} | |
}, [DOMContent, DOMApplied, serverSideSelector, afterDOMReady]); | |
return { | |
isServerRendered: !!serverSideEl, | |
DOMContent, | |
DOMApplied: !!DOMApplied, | |
}; | |
}; |
This file contains 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
# frozen_string_literal: true | |
class SomeController < ApplicationController | |
def index | |
@person = Person.find params[:person_id] | |
authorize! :view, @person | |
@items = @person.items | |
render layout: include_layout? # probably only serves up inner, of-value body | |
end | |
private | |
# think about putting this in ApplicationController | |
def include_layout? | |
[false, 'false', 0, '0'].exclude? params[:layout] | |
end | |
end |
This file contains 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
// I put this in app/index.js but you do you! | |
import React, { Suspense, lazy } from "react"; | |
import PropTypes from "prop-types"; | |
import { Route, Switch } from "react-router-dom"; | |
import { SharedProvider } from "../../../modules/something/context"; // to easily share state without going full redux | |
import SharedHeader from "../../widgets/employees/Header"; // some shared header | |
import SomeMenu from "./SomeMenu"; // some shared navigation | |
import { ThreeDotLoader } from "../../shared"; // simple loader | |
const SomePage = lazy(() => import("./SomePage")); | |
// ... others here too | |
const SomeApp = _ => ( | |
<SharedProvider employeeId={employeeId}> | |
<SharedHeader /> | |
<SomeMenu /> | |
<Suspense | |
fallback={ | |
<ThreeDotLoader className="three-dot-loader three-dot-loader--suspense" /> | |
} | |
> | |
<Switch> | |
<Route exact path="/your_route/:someId" component={SomePage} /> | |
// others here too! | |
</Switch> | |
</Suspense> | |
</SharedProvider> | |
); | |
export default SomeApp; |
This file contains 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
import React from "react"; | |
import PropTypes from "prop-types"; | |
import { useServerRender } from "../../../lib/customHooks"; // your file structure is FINE!! | |
const SomePage = ({ match: { url } }) => { | |
const { isServerRendered, DOMContent } = useServerRender( | |
url, // forward url from react-router-dom | |
"#availability-page", // a known wrapper of the content you're looking to fetch 'n place | |
loadAvailability // loads jQuery events from app/assets/javascripts/availability.js | |
); | |
if (!!DOMContent) { | |
return <div dangerouslySetInnerHTML={DOMContent} />; | |
} | |
return isServerRendered ? null : ( | |
<p style={{ maxWidth: 900, margin: "auto" }}>Loading...</p> | |
); | |
}; | |
SomePage.propTypes = { | |
match: PropTypes.object, | |
}; | |
export default SomePage; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment