Skip to content

Instantly share code, notes, and snippets.

@danielpowell4
Last active May 24, 2019 18:49
Show Gist options
  • Save danielpowell4/7ad0d3b646e20ce41c6e4645acbdc536 to your computer and use it in GitHub Desktop.
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
// 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,
};
};
# 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
// 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;
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