Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Want more from your frontend framework!

Source code for my Telia Full Stack Feast talk "Want more from your frontend framework!" (slides) (6/2020), comparing Redux with REST and a Fulcro with Pathom (Graph API).

Use case we are implementing: Show “hot deals” in your webshop, loaded on-demand.

PS: For the sake of simplicity I am cheating a little and presenting the Fulcro HotDeals component as a root component. If it was used as a child, we would need to either change the a Link Query or make sure that the :deals are presented as a property on the parent component. Also, I use unqualified keys for brevity. This is not recommended in practice.

/** Traditional Redux + REST implementation */
import React from 'react';
import Redux from 'react-redux';
// ################################################# FRONTEND
// FRONTEND 1 - UI: HotDeals.jsx
export default HotDeals = Redux.connect(
(state) => _.pick(state, ["deals", "dealsError", "dealsLoading"]),
{ loadHotDeals } // defined below
)(function HotDeals({deals, dealsError, dealsLoading, loadHotDeals}){
React.useEffect(() => loadHotDeals(), []) // on mount
if (!deals || dealsLoading) return <p>Loading....</p>
if (dealsError) return <p>Something went wrong</p>
return <ul>{deals.map(deal => <Deal {...deal}/>)}</ul>
})
// FRONTEND 2: actions.js
export function loadHotDeals() {
return {
type: "LOAD_HOT_DEALS",
promise: fetchHotDeals()
}
}
// FRONTEND 3: backend-client.js
export function fetchHotDeals() {
return fetch('https://backend/hot-deals')
.then(res => res.json())
}
// FRONTEND 4: reducer.js
//import { handle } from 'redux-pack'; // 1 Promise action -> 4 events
export function reducer(state = myInitialState, action) {
const { type, payload } = action;
switch (type) {
case "LOAD_HOT_DEALS":
// The UI expects deals, dealsLoading, dealsError:
return handle(state, action, {
start: prevState => ({
...prevState,
dealsLoading: true, dealsError: null
}),
finish: prevState =>
({ ...prevState, dealsLoading: false }),
failure: prevState =>
({ ...prevState, dealsError: payload }),
success: prevState =>
({ ...prevState, deals: payload })
});
// ... repeat ∀ data sources ...
}
}
// ################################################## BACKEND
// BACKEND - BUSINESS: webshop.js
async function hotDeals(env) { return ...; }
// BACKEND - PLUMBING: controller.js
router.get('/hot-deals', async(req, res) =>
res.json(await webshop.hotDeals(req.env)));
;; Implementation based on Fulcro and Graph API (via Pathom)
;; ################################################# FRONTEND
;; FRONTEND - all of it: deals.cljs
(def deal-factory (fcomp/factory Deal)) ;; Deal component not shown
;; Syntax: [1, 2, ...] = "array", {:key "value", ...} = map, (something ...) = invoke something (a function, ...)
(defsc HotDeals [this props]
{:query [{:deals (fcomp/get-query Deal)} [ffetch/marker-table :deals-marker]] ; (1) (**)
:use-hooks? true}
(hooks/use-effect (fn [] (ffetch/load! ; (**)
this :deals Deal ; (2)
{:marker :deals-marker})) ; (**)
[])
;; NOTE: We say we need :deals in the query (1), we get them from props (3),
;; we load! them from the server (2) - all 3 must match (and Fulcro checks it)
(let [marker (get props [ffetch/marker-table :deals-marker])]
(cond
(ffetch/loading? marker) (p "Loading...")
(ffetch/failed? marker) (p "Something went wrong...")
:else (ul (map deal-factory (get props :deals)))))) ; (3)
;; (**) - highlights: 1) declarative data needs; 2) built-in load!; 3) built-in loading/failed tracking
;; ################################################# BACKEND
;; BACKEND - BUSINESS: webshop.clj
(defn hot-deals [env] ...)
;; BACKEND - PLUMBING: graph-api.clj
(pc/defresolver hot-deals [env _]
{::pc/input #{}
::pc/output [{:deals [:deal/id :deal/title ...]}]} ; (4)
{:deals (hot-deals env)})
;; NOTE: The output key :deals, (4) matches the key FE queries for
;; In config:
... (pc/connect-plugin {::pc/register [hot-deals ...]}) ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.