Component Composition

L&L April 2022
Steve Olsen

GOALS:
- To learn something new
- Smile twice (or more)
Ways to shave




Pros and cons
- No "one right" answer!
- Tradeoffs abound
- Implementation details mater, too!
# The problem
What's all the ado?

Code reuse is hard

- DRY
- Inheritance
- Libraries
- Gists
- Functions
- Loops
- etc.




# Functions
Code that processes data





What is a function?
A series of statements
that are common to solving a problem
PURE function:
-
Single purpose ideally
-
(rule of thumb: 10 statements, early exit)
-
-
No external state or data dependencies
-
(i.e. no closures, i/o)
-
-
REPEATABLE / idempotent
-
Highly refactorable
-
Highly testable
-
Highly reusable
SIDE-EFFECT function:
-
Single purpose ideally
-
(rule of thumb: 10 statements, early exit)
-
-
HAS external state or data dependencies
-
(i.e. closures, i/o)
-
-
CHANGE TO STATE
-
NON-REPEATABLE / non-idempotent
-
HARDer to refactor
-
HARDer to test
-
HARDer to reuse
SIDE-EFFECT function:
PURE function:
export const add = (a, b) => a + bimport fs from "fs"
export const saveToFile = async (filename, text) => {
return fs.writeFile(filename, text)
}
export const readFile = async (filename) => {
return fs.readFile(filename)
}
export const callApi = async (id) => {
return fetch(`http://123.com/${id}`).then(r => r.json())
}
let id = 0
export const getId() {
return id++
}
Date.now() === '?????'Example Call Stack
main()






Tell Me More.
# Pattern
"composition"

OOP: “Composition over inheritance”
contain instances, instead of
inheriting from a parent
FP: "Onion Layers"
wrappers that intercept an
inner function's execution
Each "onion layer" is a function

Official React Design Patterns
Composition is #1!
"An {X} that returns an {X}"
-
HOF: A function that returns a function
-
HOC: A Component that returns a Component
"Higher Order {X}"
const g = f(x)
const G = <F><X/></F>Practical Examples:
// Constructor ///////////
const handleButton = (index) => () => {
console.log("Clicked button #" + index)
}
...
{[1,2,3].map((number, idx) =>
<button onClick={handleButton(idx)}>{number}</button>)}
// Decorator ///////////
const timeFunc = (cb) => () => {
const now = Date.now()
cb()
console.log("TIME LAPSED (ms):", Date.now() - now)
}
const looper = (times) {
for (let i = 0; i < times; i++) {
console.log("i=" + i)
}
}
const timedLoop = timeFunc(() => looper(99999))
timedLoop() // TIME LAPSED (ms): 4
timedLoop() // TIME LAPSED (ms): 6
timedLoop() // TIME LAPSED (ms): 3Curry:
// Theory
const add = (a, b) => a + b
const addBy5 = add(5)
addBy5(5) === 10A function that returns a function
until all its arguments are provided.
JavaScript does not provide this natively,
but there are wrappers to provide it!
Wrapper Pattern examples
- Facade
- Adapter
- Middleware
- Interceptor
- Decorator
Eg. [ [ pres ] state({initial}) ]
Eg. [ [ apiGetUser ] debounce({seconds}) ]Bonus pattern: reducers
PURE / observer / event bus
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'cnt/increment':
return {count: state.count + 1};
case 'cnt/decrement':
return {count: state.count - 1};
// default:
// throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'cnt/decrement'})}>-</button>
<button onClick={() => dispatch({type: 'cnt/increment'})}>+</button>
</>
);
}# Code Quality
aka. low ratio of wtf-per-minute

Code Quality Phases
OPTIMIZED (5%)
MAINTAINABLE (90%+)
WORKING (5%)
refactor, test
heavy test
Misc Notes:
- Refactoring (breaking apart, polishing) is critical!
-
All essential code must go somewhere —
in another well-named file (follow naming conventions)
-
split up per responsibility
-
benefits:
-
less scrolling/hopping around file ("fits in your head")
- fewer chances for git merge conflicts
- separate config/types/consts separately from implementation module for 3rd party importing
-
-
- VS Code: set "Window Title":
-
${dirty} ${activeEditorMedium}${separator}${rootName}
-

Coupling
-
"Connascence": level of coupling
-
Dimensions: strength, degree, locality
-
Not all coupling is "bad"
-
Tradeoffs made by the developer
-
important to understand them!
-
# Components
they're highly functional

Separation of concerns
-
data presentation
-
event handling
-
state manipulation
-
Data validation [assert]
-
Data manipulation [map/reduce]
-
Data API calls [I/O]
-
Analytics / metics / logging [monitoring I/O]
Separation of concerns
-
data presentation
-
event handling
-
state manipulation
-
Data validation [assert]
-
Data manipulation [map/reduce]
-
Data API calls [I/O]
-
Analytics / metics / logging [monitoring I/O]
Separation of concerns
-
data presentation
-
event handling
-
state manipulation
-
Data validation [assert]
-
Data manipulation [map/reduce]
-
Data API calls [I/O]
-
Analytics / metics / logging [monitoring I/O]
Pres
Contain
Func/
BusLog
Separation of concerns
- data presentation
- event handling
- inner state manipulation
a1) Presentation components:
-
app state manipulation
-
wire up business logic to presentation component
a2) Container components:
- data API calls [I/O]
- data validation [assert]
-
data manipulation [map/reduce]
-
analytics / metics / logging [monitoring I/O]
b) Business Logic functions:
Utitlity Components
-
Goal: reduce copy/paste, DRY!
-
Create new component APIs that encapsulate common patterns and conventions
-
Often a parent/child relationship
-
Common ones are shared:
-
maximize reuse
-
minimize redundancy
-
-
Think about this like “a bucket of specific Figma assets” ala drag-and-drop reuse
-
Eg. Carousel, Select box, Navigation
-
Tips and resources
- Other names for this component pattern:
- “smart/dumb”, “widget/presentation”, “effect/pure”
- Check the imports! This will tell you a lot about what the component is doing. (Maybe it's too much?)
- Refactoring normally creates more files, not fewer
- Naming files well is crucial for maintenance!
- Patern links:
-
https://www.patterns.dev/posts/presentational-container-pattern/ -
https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0 -
https://gist.github.com/chantastic/fc9e3853464dffdb1e3c
-

Standard non-component files:
- index.ts : “public API” to be imported by other components
- styles.ts : CSS
- types.ts : interfaces, types
-
config.ts : enums, etc.
- Example: urlBase, userTypes
- state.ts or reducer.ts *** TBD ***
# Hands-on

Live Example
-
Basic WIP: React
- Advanced reference: React Native Web
Review Snap components
front_end/src/views-mui/header/header-omni/header-omni-mobile.component.js link
- mixed together presentation, state, event handling
- separate components, new files
front_end/src/components-mui/header-desktop/header-desktop-nav.container.js link
-
component duplication, event handling, io
-
utility components, new files, decouple BL
snapcommerce-spa/src/routers/auth-required.tsx link
- multiple components, mixed purposes, no testing (because…. how?)
- sub-components, new files - FIXED!!
# THANK YOU
Questions?
"Good architecture has big benefits, but it needs: education, discipline, and enforcement."
Component Composition
By solsen-tl
Component Composition
- 25