- Sandpack Patterns
- Quick Start Template
- Most examples are single-file. Copy this and modify:
`js- import { useState } from 'react';
- export default function Example() {
- const [value, setValue] = useState(0);
- return (
- Clicked {value} times
- );
- }
`- File Naming
- Pattern
- Usage
- ```js
- Main file (no prefix)
- ```js src/FileName.js
- Supporting files
- ```js src/File.js active
- Active file (reference pages)
- ```js src/data.js hidden
- Hidden files
- ```css
- CSS styles
- ```json package.json
- External dependencies
- Critical:
- Main file must have
- export default
- .
- Line Highlighting
- ```js
- function Example() {
- // Lines 2-4
- // will be
- // highlighted
- return null;
- }
- Code References (numbered callouts)
- ```js [[1, 4, "age"], [2, 4, "setAge"]]
- // Creates numbered markers pointing to "age" and "setAge" on line 4
- Expected Errors (intentionally broken examples)
- ```js {expectedErrors: {'react-compiler': [7]}}
- // Line 7 shows as expected error
- Multi-File Example
- ```js src/App.js
- import Gallery from './Gallery.js';
- export default function App() {
- return
; - }
- export
- default
- function
- Gallery
- (
- )
- {
- return
- <
- h1
- >
- Gallery
- <
- /
- h1
- >
- ;
- }
- h1
- {
- color
- :
- purple
- ;
- }
- External Dependencies
- ```js
- import { useImmer } from 'use-immer';
- // ...
- {
- "dependencies"
- :
- {
- "immer"
- :
- "1.7.3"
- ,
- "use-immer"
- :
- "0.5.1"
- ,
- "react"
- :
- "latest"
- ,
- "react-dom"
- :
- "latest"
- ,
- "react-scripts"
- :
- "latest"
- }
- }
- Code Style in Sandpack (Required)
- Sandpack examples are held to strict code style standards:
- Function declarations
- for components (not arrows)
- e
- for event parameters
- Single quotes
- in JSX
- const
- unless reassignment needed
- Spaces in destructuring
- :
- ({ props })
- not
- ({props})
- Two-line createRoot
-
- separate declaration and render call
- Multiline if statements
- always use braces
Don't Create Hydration Mismatches
Sandpack examples must produce the same output on server and client:
// 🚫 This will cause hydration warnings
export
default
function
App
(
)
{
const
isClient
=
typeof
window
!==
'undefined'
;
return
<
div
{ isClient ? 'Client' : 'Server' } < / div
; } Use Ref for Non-Rendered State // 🚫 Don't trigger re-renders for non-visual state const [ mounted , setMounted ] = useState ( false ) ; useEffect ( ( ) => { setMounted ( true ) ; } , [ ] ) ; // ✅ Use ref instead const mounted = useRef ( false ) ; useEffect ( ( ) => { mounted . current = true ; } , [ ] ) ; forwardRef and memo Patterns forwardRef - Use Named Function // ✅ Named function for DevTools display name const MyInput = forwardRef ( function MyInput ( props , ref ) { return < input { ... props } ref = { ref } /
; } ) ; // 🚫 Anonymous loses name const MyInput = forwardRef ( ( props , ref ) => { ... } ) ; memo - Use Named Function // ✅ Preserves component name const Greeting = memo ( function Greeting ( { name } ) { return < h1
Hello , { name } < / h1
; } ) ; Line Length Prose: ~80 characters Code: ~60-70 characters Break long lines to avoid horizontal scrolling Anti-Patterns Pattern Problem Fix const Comp = () => {} Not standard function Comp() {} onClick={(event) => ...} Conflicts with global onClick={(e) => ...} useState for non-rendered values Re-renders Use useRef Reading window during render Hydration mismatch Check in useEffect Single-line if without braces Harder to debug Use multiline with braces Chained createRoot().render() Less clear Two statements //... without space Inconsistent // ... with space Tabs Inconsistent 2 spaces ReactDOM.render Deprecated Use createRoot Fake package names Confusing Use './your-storage-layer' PropsWithChildren Outdated children?: ReactNode Missing key in lists Warnings Always include key Additional Code Quality Rules Always Include Keys in Lists // ✅ Correct { items . map ( item => < li key = { item . id }
{ item . name } < / li
) } // 🚫 Wrong - missing key { items . map ( item => < li
{ item . name } < / li
) } Use Realistic Import Paths // ✅ Correct - descriptive path import { fetchData } from './your-data-layer' ; // 🚫 Wrong - looks like a real npm package import { fetchData } from 'cool-data-lib' ; Console.log Labels // ✅ Correct - labeled for clarity console . log ( 'User:' , user ) ; console . log ( 'Component Stack:' , errorInfo . componentStack ) ; // 🚫 Wrong - unlabeled console . log ( user ) ; Keep Delays Reasonable // ✅ Correct - 1-1.5 seconds setTimeout ( ( ) => setLoading ( false ) , 1000 ) ; // 🚫 Wrong - too long, feels sluggish setTimeout ( ( ) => setLoading ( false ) , 3000 ) ; Updating Line Highlights When modifying code in examples with line highlights ( {2-4} ), always update the highlight line numbers to match the new code. Incorrect line numbers cause rendering crashes. File Name Conventions Capitalize file names for component files: Gallery.js not gallery.js After initially explaining files are in src/ , refer to files by name only: Gallery.js not src/Gallery.js Naming Conventions in Code Components: PascalCase Profile , Avatar , TodoList , PackingList State variables: Destructured pattern const [count, setCount] = useState(0) Booleans: [isOnline, setIsOnline] , [isPacked, setIsPacked] Status strings: 'typing' , 'submitting' , 'success' , 'error' Event handlers: handleClick , handleSubmit , handleAddTask Props for callbacks: onClick , onChange , onAddTask , onSelect Custom Hooks: useOnlineStatus , useChatRoom , useFormInput Reducer actions: Past tense: 'added' , 'changed' , 'deleted' Snake_case compounds: 'changed_selection' , 'sent_message' Updater functions: Single letter setCount(n => n + 1) Pedagogical Code Markers Wrong vs right code: // 🔴 Avoid: redundant state and unnecessary Effect // ✅ Good: calculated during rendering Console.log for lifecycle teaching: console . log ( '✅ Connecting...' ) ; console . log ( '❌ Disconnected.' ) ; Server/Client Labeling // Server Component async function Notes ( ) { const notes = await db . notes . getAll ( ) ; } // Client Component "use client" export default function Expandable ( { children } ) { const [ expanded , setExpanded ] = useState ( false ) ; } Bundle Size Annotations import marked from 'marked' ; // 35.9K (11.2K gzipped) import sanitizeHtml from 'sanitize-html' ; // 206K (63.3K gzipped) Sandpack Example Guidelines Package.json Rules Include package.json when: Using external npm packages (immer, remarkable, leaflet, toastify-js, etc.) Demonstrating experimental/canary React features Requiring specific React versions ( react: beta , react: 19.0.0-rc-* ) Omit package.json when: Example uses only built-in React features No external dependencies needed Teaching basic hooks, state, or components Always mark package.json as hidden:
``json package.json hidden { "dependencies": { "react": "latest", "react-dom": "latest", "react-scripts": "latest", "immer": "1.7.3" } } **Version conventions:** - Use"latest"` for stable features - Use exact versions only when compatibility requires it - Include minimal dependencies (just what the example needs)
Hidden File Patterns
Always hide these file types:
| File Type | Reason |
|-----------|--------|
| package.json | Configuration not the teaching point |
| sandbox.config.json | Sandbox setup is boilerplate |
| public/index.html | HTML structure not the focus |
| src/data.js | When it contains sample/mock data |
| src/api.js | When showing API usage, not implementation |
| src/styles.css | When styling is not the lesson |
| src/router.js | Supporting infrastructure |
| src/actions.js | Server action implementation details |
Rationale:
- Reduces cognitive load
- Keeps focus on the primary concept
- Creates cleaner, more focused examples
Example:
mdxjs src/data.js hidden
export const items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
];
Active File Patterns
Mark as active when:
- File contains the primary teaching concept
- Learner should focus on this code first
- Component demonstrates the hook/pattern being taught
Effect of the active marker:
- Sets initial editor tab focus when Sandpack loads
- Signals "this is what you should study"
- Works with hidden files to create focused examples
Most common active file: src/index.js or src/App.js
Example:
mdxjs src/App.js active
// This file will be focused when example loads
export default function App() {
// ...
}
File Structure Guidelines
| Scenario | Structure | Reason |
|---|---|---|
| Basic hook usage | Single file | Simple, focused |
| Teaching imports | 2-3 files | Shows modularity |
| Context patterns | 4-5 files | Realistic structure |
| Complex state | 3+ files | Separation of concerns |
| Single File Examples (70% of cases): | ||
| - Use for simple concepts | ||
| - 50-200 lines typical | ||
| - Best for: Counter, text inputs, basic hooks | ||
| Multi-File Examples (30% of cases): | ||
| - Use when teaching modularity/imports | ||
| - Use for context patterns (4-5 files) | ||
| - Use when component is reused | ||
| File Naming: | ||
- Main component: App.js (capitalized) |
||
- Component files: Gallery.js, Button.js (capitalized) |
||
- Data files: data.js (lowercase) |
||
- Utility files: utils.js (lowercase) |
||
- Context files: TasksContext.js (named after what they provide) |
||
| ### Code Size Limits | ||
| - Single file: <200 lines | ||
| - Multi-file total: 150-300 lines | ||
| - Main component: 100-150 lines | ||
| - Supporting files: 20-40 lines each | ||
| ### CSS Guidelines | ||
| Always: | ||
| - Include minimal CSS for demo interactivity | ||
- Use semantic class names (.panel, .button-primary, .panel-dark) |
||
| - Support light/dark themes when showing UI concepts | ||
| - Keep CSS visible (never hidden) | ||
| Size Guidelines: | ||
| - Minimal (5-10 lines): Basic button styling, spacing | ||
| - Medium (15-30 lines): Panel styling, form layouts | ||
| - Complex (40+ lines): Only for layout-focused examples |