Installations
npm install use-sidecar
Score
96.6
Supply Chain
99.1
Quality
75.6
Maintenance
100
Vulnerability
100
License
Developer
theKashey
Developer Guide
Module System
CommonJS
Min. Node Version
>=10
Typescript Support
Yes
Node Version
16.3.0
NPM Version
7.15.1
Statistics
106 Stars
52 Commits
10 Forks
5 Watching
2 Branches
5 Contributors
Updated on 12 Nov 2024
Bundle Size
3.95 kB
Minified
1.53 kB
Minified + Gzipped
Languages
TypeScript (90.04%)
JavaScript (9.96%)
Total Downloads
Cumulative downloads
Total Downloads
641,295,706
Last day
-0.9%
1,304,629
Compared to previous day
Last week
4.2%
6,826,081
Compared to previous week
Last month
11.3%
28,354,794
Compared to previous month
Last year
104.9%
297,831,814
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
Dependencies
2
Peer Dependencies
2
UI/Effects code splitting pattern
- read the original article to understand concepts behind.
- read how Google split view and logic.
- watch how Facebook defers "interactivity" effects.
Terminology:
sidecar
- non UI component, which may carry effects for a paired UI component.UI
- UI component, which interactivity is moved to asidecar
.
UI
is a view, sidecar
is the logic for it. Like Batman(UI) and his sidekick Robin(effects).
Concept
-
a
package
exposes 3 entry points using a nestedpackage.json
format:- default aka
combination
, and lets hope tree shaking will save you UI
, with only UI partsidecar
, with all the logic-
UI
+sidecar
===combination
. The size ofUI+sidecar
might a bit bigger than size of theircombination
. Use size-limit to control their size independently.
- default aka
-
package uses a
medium
to talk with own sidecar, breaking explicit dependency. -
if package depends on another sidecar package:
- it shall export dependency side car among own sidecar.
- package imports own sidecar via
medium
, thus able to export multiple sidecars via one export.
-
final consumer uses
sidecar
oruseSidecar
to combine pieces together.
Rules
UI
components might use/import any otherUI
componentssidecar
could use/import any othersidecar
That would form two different code branches, you may load separately - UI first, and effect sidecar later. That also leads to a obvious consequence - one sidecar may export all sidecars.
-
to decouple
sidecars
from module exports, and be able to pick "the right" one at any point you have to useexportSidecar(medium, component)
to export it, and use the samemedium
to import it back. -
this limitation is for libraries only, as long as in the usercode you might dynamically import whatever and whenever you want.
-
useMedium
is always async - action would be executed in a next tick, or on the logic load. -
sidecar
is always async - is does not matter have you loaded logic or not - component would be rendered at least in the next tick.
except
medium.read
, which synchronously read the data from a medium, andmedium.assingSyncMedium
which changesuseMedium
to be sync.
SSR and usage tracking
Sidecar pattern is clear:
- you dont need to use/render any
sidecars
on server. - you dont have to load
sidecars
prior main render.
Thus - no usage tracking, and literally no SSR. It's just skipped.
API
createMedium()
- Type: Util. Creates shared effect medium for algebraic effect.
- Goal: To decouple modules from each other.
- Usage:
use
in UI side, andassign
from side-car. All effects would be executed. - Analog: WeakMap, React.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
1const medium = createMedium(defaultValue); 2const cancelCb = medium.useMedium(someData); 3 4// like 5useEffect(() => medium.useMedium(someData), []); 6 7medium.assignMedium(someDataProcessor) 8 9// createSidecarMedium is a helper for createMedium to create a "sidecar" symbol 10const effectCar = createSidecarMedium();
! For consistence
useMedium
is async - sidecar load status should not affect function behavior, thus effect would be always executed at least in the "next tick". You may alter this behavior by usingmedium.assingSyncMedium
.
exportSidecar(medium, component)
- Type: HOC
- Goal: store
component
insidemedium
and return external wrapper - Solving: decoupling module exports to support exporting multiple sidecars via a single entry point.
- Usage: use to export a
sidecar
- Analog: WeakMap
1import {effectCar} from './medium'; 2import {EffectComponent} from './Effect'; 3// !!! - to prevent Effect from being imported 4// `effectCar` medium __have__ to be defined in another file 5// const effectCar = createSidecarMedium(); 6export default exportSidecar(effectCar, EffectComponent);
sidecar(importer)
- Type: HOC
- Goal: React.lazy analog for code splitting, but does not require
Suspense
, might provide error failback. - Usage: like React.lazy to load a side-car component.
- Analog: React.Lazy
1import {sidecar} from "use-sidecar"; 2const Sidecar = sidecar(() => import('./sidecar'), <span>on fail</span>); 3 4<> 5 <Sidecar /> 6 <UI /> 7</>
Importing exportedSidecar
Would require additional prop to be set - <Sidecar sideCar={effectCar} />
useSidecar(importer)
- Type: hook, loads a
sideCar
using providedimporter
which shall follow React.lazy API - Goal: to load a side car without displaying any "spinners".
- Usage: load side car for a component
- Analog: none
1import {useSidecar} from 'use-sidecar'; 2 3const [Car, error] = useSidecar(() => import('./sideCar')); 4return ( 5 <> 6 {Car ? <Car {...props} /> : null} 7 <UIComponent {...props}> 8 </> 9);
Importing exportedSideCar
You have to specify effect medium to read data from, as long as export itself is empty.
1import {useSidecar} from 'use-sidecar'; 2 3/* medium.js: */ export const effectCar = useMedium({}); 4/* sideCar.js: */export default exportSidecar(effectCar, EffectComponent); 5 6const [Car, error] = useSidecar(() => import('./sideCar'), effectCar); 7return ( 8 <> 9 {Car ? <Car {...props} /> : null} 10 <UIComponent {...props}> 11 </> 12);
renderCar(Component)
- Type: HOC, moves renderProp component to a side channel
- Goal: Provide render prop support, ie defer component loading keeping tree untouched.
- Usage: Provide
defaults
and use them until sidecar is loaded letting you code split (non visual) render-prop component - Analog: - Analog: code split library like react-imported-library or @loadable/lib.
1import {renderCar, sidecar} from "use-sidecar"; 2const RenderCar = renderCar( 3 // will move side car to a side channel 4 sidecar(() => import('react-powerplug').then(imports => imports.Value)), 5 // default render props 6 [{value: 0}] 7); 8 9<RenderCar> 10 {({value}) => <span>{value}</span>} 11</RenderCar>
setConfig(config)
1setConfig({
2 onError, // sets default error handler
3});
Examples
Deferred effect
Let's imagine - on element focus you have to do "something", for example focus anther element
Original code
1onFocus = event => { 2 if (event.currentTarget === event.target) { 3 document.querySelectorAll('button', event.currentTarget) 4 } 5}
Sidecar code
- Use medium (yes, .3)
1// we are calling medium with an original event as an argument 2const onFocus = event => focusMedium.useMedium(event);
- Define reaction
1// in a sidecar
2
3// we are setting handler for the effect medium
4// effect is complicated - we are skipping event "bubbling",
5// and focusing some button inside a parent
6focusMedium.assignMedium(event => {
7 if (event.currentTarget === event.target) {
8 document.querySelectorAll('button', event.currentTarget)
9 }
10});
11
- Create medium
Having these constrains - we have to clone
event
, as long as React would eventually reuse SyntheticEvent, thus not preservetarget
andcurrentTarget
.
1// 2const focusMedium = createMedium(null, event => ({...event}));
Now medium side effect is ok to be async
Example: Effect for react-focus-lock - 1kb UI, 4kb sidecar
Medium callback
Like a library level code splitting
Original code
1import {x, y} from './utils'; 2 3useEffect(() => { 4 if (x()) { 5 y() 6 } 7}, []);
Sidecar code
1// medium
2const utilMedium = createMedium();
3
4// utils
5const x = () => { /* ... */};
6const y = () => { /* ... */};
7
8// medium will callback with exports exposed
9utilMedium.assignMedium(cb => cb({
10 x, y
11}));
12
13
14// UI
15// not importing x and y from the module system, but would be given via callback
16useEffect(() => {
17 utilMedium.useMedium(({x,y}) => {
18 if (x()) {
19 y()
20 }
21 })
22}, []);
- Hint: there is a easy way to type it
1const utilMedium = createMedium<(cb: typeof import('./utils')) => void>();
Example: Callback API for react-focus-lock
Split effects
Lets take an example from a Google - Calendar app, with view and logic separated. To be honest - it's not easy to extract logic from application like calendar - usually it's tight coupled.
Original code
1const CalendarUI = () => { 2 const [date, setDate] = useState(); 3 const onButtonClick = useCallback(() => setDate(Date.now), []); 4 5 return ( 6 <> 7 <input type="date" onChange={setDate} value={date} /> 8 <input type="button" onClick={onButtonClick}>Set Today</button> 9 </> 10 ) 11}
Sidecar code
1const CalendarUI = () => { 2 const [events, setEvents] = useState({}); 3 const [date, setDate] = useState(); 4 5 return ( 6 <> 7 <Sidecar setDate={setDate} setEvents={setEvents}/> 8 <UILayout {...events} date={date}/> 9 </> 10 ) 11} 12 13const UILayout = ({onDateChange, onButtonClick, date}) => ( 14 <> 15 <input type="date" onChange={onDateChange} value={date} /> 16 <input type="button" onClick={onButtonClick}>Set Today</button> 17 </> 18); 19 20// in a sidecar 21// we are providing callbacks back to UI 22const Sidecar = ({setDate, setEvents}) => { 23 useEffect(() => setEvents({ 24 onDateChange:setDate, 25 onButtonClick: () => setDate(Date.now), 26 }), []); 27 28 return null; 29}
While in this example this looks a bit, you know, strange - there are 3 times more code
that in the original example - that would make a sense for a real Calendar, especially
if some helper library, like moment
, has been used.
Example: Effect for react-remove-scroll - 300b UI, 2kb sidecar
Licence
MIT
No vulnerabilities found.
No security vulnerabilities found.
Other packages similar to use-sidecar
@spotlightjs/sidecar
A small proxy server to capture and forward data from backend services to Spotlight.
@docknetwork/node-types
Types for Dock's Substrate node
@spotlightjs/spotlight
Spotlight - Sentry for development. Containing the overlay and the sidecar.
@sanity/default-layout
The default layout components for Sanity