A curated collection of best practices, patterns, and recommendations for building high-quality Teachfloor apps.
Development
Project Structure
Organize your code logically:
my-app/
├── src/
│ ├── index.js # Entry point
│ ├── views/ # Viewport components
│ │ ├── App.jsx
│ │ ├── CourseView.jsx
│ │ └── SettingsView.jsx
│ ├── components/ # Reusable components
│ │ ├── Header.jsx
│ │ └── shared/
│ ├── hooks/ # Custom hooks
│ │ ├── useAppData.js
│ │ └── usePreferences.js
│ ├── utils/ # Utilities
│ │ ├── api.js
│ │ └── formatting.js
│ └── constants/ # Constants
│ └── config.js
├── public/
└── teachfloor-app.json
Component Design
Do : Create small, focused components
// ✅ Good
function NotesList ({ notes }) {
return notes. map ( note => < NoteItem key = {note.id} note = {note} />)
}
function NoteItem ({ note }) {
return < div >{note.title}</ div >
}
Don't : Create monolithic components
// ❌ Bad
function NotesApp () {
// 500 lines of code...
}
Custom Hooks
Extract logic into reusable hooks:
// ✅ Good
function useNotes () {
const [ notes , setNotes ] = useState ([])
const [ loading , setLoading ] = useState ( true )
useEffect (() => {
loadNotes ()
}, [])
async function loadNotes () {
const data = await retrieve ( 'notes' , 'userdata' )
setNotes (data || [])
setLoading ( false )
}
async function addNote ( note ) {
const updated = [ ... notes, note]
await store ( 'notes' , updated, 'userdata' )
setNotes (updated)
}
return { notes, loading, addNote }
}
// Usage
function MyComponent () {
const { notes , loading , addNote } = useNotes ()
// ...
}
Error Boundaries
Implement error boundaries:
class ErrorBoundary extends React . Component {
state = { hasError: false }
static getDerivedStateFromError ( error ) {
return { hasError: true }
}
componentDidCatch ( error , errorInfo ) {
console. error ( 'App error:' , error, errorInfo)
}
render () {
if ( this .state.hasError) {
return (
< Container >
< Text >Something went wrong. Please refresh.</ Text >
</ Container >
)
}
return this .props.children
}
}
// Usage
< ErrorBoundary >
< App />
</ ErrorBoundary >
Performance
Lazy Loading
Load components only when needed:
import { lazy, Suspense } from 'react'
const HeavyComponent = lazy (() => import ( './HeavyComponent' ))
function App () {
return (
< Suspense fallback = {< Loader />}>
< HeavyComponent />
</ Suspense >
)
}
Memoization
Memoize expensive calculations:
import { useMemo } from 'react'
function AnalyticsDashboard ({ data }) {
const statistics = useMemo (() => {
// Expensive calculation
return calculateStatistics (data)
}, [data])
return < div >{statistics}</ div >
}
Debouncing
Debounce frequent operations:
import { useCallback, useRef } from 'react'
function SearchInput () {
const timeoutRef = useRef ( null )
const debouncedSearch = useCallback (( query ) => {
if (timeoutRef.current) {
clearTimeout (timeoutRef.current)
}
timeoutRef.current = setTimeout (() => {
performSearch (query)
}, 500 )
}, [])
return (
< TextInput
placeholder = "Search..."
onChange = {( e ) => debouncedSearch (e.target.value)}
/>
)
}
Bundle Size
Minimize bundle size:
// ✅ Good: Import only what you need
import { Button, Text } from '@teachfloor/extension-kit'
// ❌ Bad: Import everything
import * as ExtensionKit from '@teachfloor/extension-kit'
Caching
Implement data caching:
const cache = new Map ()
async function getCachedData ( key , fetchFn , ttl = 60000 ) {
const cached = cache. get (key)
if (cached && Date. now () - cached.timestamp < ttl) {
return cached.data
}
const data = await fetchFn ()
cache. set (key, { data, timestamp: Date. now () })
return data
}
Security
Input Validation
Always validate user input:
function validateEmail ( email ) {
const re = / ^ [ ^ \s@] + @ [ ^ \s@] + \. [ ^ \s@] +$ /
return re. test (email)
}
function saveUserData ( email , name ) {
if ( ! validateEmail (email)) {
throw new Error ( 'Invalid email format' )
}
if ( ! name || name. length < 2 ) {
throw new Error ( 'Name must be at least 2 characters' )
}
// Save data
}
Sanitization
Sanitize data before storage:
function sanitizeString ( str ) {
return str
. replace ( / <script [ ^ >] * > . *? < \/ script> / gi , '' )
. replace ( / < [ ^ >] + > / g , '' )
. trim ()
}
async function saveNote ( content ) {
const sanitized = sanitizeString (content)
await store ( 'note' , sanitized, 'userdata' )
}
Sensitive Data
Never expose secrets:
// ❌ Bad
const API_KEY = 'secret-key-123'
// ✅ Good: Let users provide their own keys
function Settings () {
const [ apiKey , setApiKey ] = useState ( '' )
async function saveKey () {
await store ( 'api-key' , apiKey, 'userdata' )
}
return (
< div >
< PasswordInput
value = {apiKey}
onChange = {( e ) => setApiKey (e.target.value)}
/>
< Button onClick = {saveKey}>Save</ Button >
</ div >
)
}
HTTPS Only
Always use HTTPS for external APIs:
// ✅ Good
fetch ( 'https://api.example.com/data' )
// ❌ Bad
fetch ( 'http://api.example.com/data' )
User Experience
Loading States
Show loading indicators:
function DataDisplay () {
const [ data , setData ] = useState ( null )
const [ loading , setLoading ] = useState ( true )
useEffect (() => {
loadData ()
}, [])
if (loading) {
return < Loader />
}
return < DisplayData data = {data} />
}
Empty States
Handle empty data gracefully:
function NotesList ({ notes }) {
if (notes. length === 0 ) {
return (
< Container >
< Text c = "dimmed" >No notes yet. Create your first note!</ Text >
< Button onClick = {createNote}>Create Note</ Button >
</ Container >
)
}
return notes. map ( note => < NoteItem key = {note.id} note = {note} />)
}
Error Handling
Provide helpful error messages:
function DataLoader () {
const [ error , setError ] = useState ( null )
async function loadData () {
try {
const data = await fetchData ()
setData (data)
} catch (error) {
setError ( 'Failed to load data. Please try again.' )
showToast (error.message, { color: 'red' })
}
}
if (error) {
return (
< Container >
< Text c = "red" >{error}</ Text >
< Button onClick = {loadData}>Retry</ Button >
</ Container >
)
}
return < div >Content</ div >
}
Feedback
Provide immediate feedback:
async function saveData ( data ) {
try {
showToast ( 'Saving...' , { color: 'blue' })
await store ( 'data' , data, 'userdata' )
showToast ( 'Saved successfully!' , { color: 'green' })
} catch (error) {
showToast ( 'Failed to save' , { color: 'red' })
}
}
Accessibility
Make your app accessible:
// ✅ Good: Proper labels and ARIA attributes
< Button
aria-label = "Save note"
onClick = {saveNote}
>
Save
</ Button >
< TextInput
label = "Note title"
aria-required = "true"
aria-invalid = {hasError}
/>
Code Quality
Naming Conventions
Use clear, descriptive names:
// ✅ Good
const userNotes = []
function calculateTotalScore () {}
const isAuthenticated = true
// ❌ Bad
const arr = []
function calc () {}
const flag = true
Write meaningful comments:
// ✅ Good
// Calculate the total score by summing all module scores
// and applying a weighted average based on difficulty
function calculateTotalScore ( modules ) {
// ...
}
// ❌ Bad
// This function calculates score
function calculateTotalScore ( modules ) {
// Loop through modules
// ...
}
Constants
Use constants for magic numbers:
// ✅ Good
const MAX_NOTES = 100
const CACHE_TTL = 60000 // 1 minute
if (notes. length >= MAX_NOTES ) {
// Handle limit
}
// ❌ Bad
if (notes. length >= 100 ) {
// Handle limit
}
DRY Principle
Don't repeat yourself:
// ✅ Good
function formatDate ( date ) {
return new Intl. DateTimeFormat ( 'en-US' ). format (date)
}
const created = formatDate (note.createdAt)
const updated = formatDate (note.updatedAt)
// ❌ Bad
const created = new Intl. DateTimeFormat ( 'en-US' ). format (note.createdAt)
const updated = new Intl. DateTimeFormat ( 'en-US' ). format (note.updatedAt)
Testing
Unit Tests
Write tests for utility functions:
// utils/formatting.test.js
import { formatDate } from './formatting'
test ( 'formats date correctly' , () => {
const date = new Date ( '2024-01-01' )
expect ( formatDate (date)). toBe ( '1/1/2024' )
})
Component Tests
Test component rendering:
import { render, screen } from '@testing-library/react'
import NotesList from './NotesList'
test ( 'renders empty state' , () => {
render (< NotesList notes = {[]} />)
expect (screen. getByText ( / no notes yet / i )). toBeInTheDocument ()
})
test ( 'renders notes' , () => {
const notes = [{ id: 1 , title: 'Test Note' }]
render (< NotesList notes = {notes} />)
expect (screen. getByText ( 'Test Note' )). toBeInTheDocument ()
})
Manual Testing
Test in all viewports:
□ Tested in course list page
□ Tested in course detail page
□ Tested in settings page
□ Tested on mobile viewport
□ Tested with different user roles
□ Tested with empty data
□ Tested with maximum data
□ Tested error scenarios
Deployment
Pre-Deployment Checklist
Before uploading:
□ Version incremented in manifest
□ All console.log() removed
□ Environment variables removed
□ Tests passing
□ No linting errors
□ Build succeeds
□ Bundle size acceptable
□ Permissions list complete
□ README updated
□ Changelog updated
Versioning
Follow semantic versioning:
1.0.0 → 1.0.1: Bug fix
1.0.0 → 1.1.0: New feature
1.0.0 → 2.0.0: Breaking change
Changelog
Maintain a changelog:
# Changelog
## [ 1.1.0 ] - 2024-01-15
### Added
- Export notes to PDF feature
- Dark mode support
### Fixed
- Note saving bug on slow connections
## [ 1.0.0 ] - 2024-01-01
### Added
- Initial release
- Basic note-taking functionality
Documentation
Keep documentation updated:
# My App
## Features
- Feature 1
- Feature 2
## Installation
1. Install from marketplace
2. Configure settings
## Usage
...
## Support
Email: support@example.com
Patterns
State Management
Use appropriate state management:
// Local state
const [ count , setCount ] = useState ( 0 )
// Context for app-wide state
const AppContext = createContext ()
function AppProvider ({ children }) {
const [ user , setUser ] = useState ( null )
return (
< AppContext.Provider value = {{ user, setUser }}>
{children}
</ AppContext.Provider >
)
}
// Custom hooks for complex state
function useAppState () {
const [ state , setState ] = useState (initialState)
function updateState ( updates ) {
setState ( prev => ({ ... prev, ... updates }))
}
return [state, updateState]
}
API Integration
Structure API calls:
// api.js
class API {
constructor ( baseURL ) {
this .baseURL = baseURL
}
async get ( endpoint ) {
const response = await fetch ( `${ this . baseURL }${ endpoint }` )
if ( ! response.ok) throw new Error ( 'API error' )
return response. json ()
}
async post ( endpoint , data ) {
const response = await fetch ( `${ this . baseURL }${ endpoint }` , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify (data)
})
if ( ! response.ok) throw new Error ( 'API error' )
return response. json ()
}
}
export const api = new API ( 'https://api.example.com' )
// Usage
const data = await api. get ( '/notes' )
await api. post ( '/notes' , { title: 'New Note' })
Form Handling
Handle forms effectively:
function NoteForm ({ onSave }) {
const [ values , setValues ] = useState ({ title: '' , content: '' })
const [ errors , setErrors ] = useState ({})
function validate () {
const errors = {}
if ( ! values.title) errors.title = 'Title is required'
if ( ! values.content) errors.content = 'Content is required'
return errors
}
async function handleSubmit ( e ) {
e. preventDefault ()
const errors = validate ()
if (Object. keys (errors). length > 0 ) {
setErrors (errors)
return
}
try {
await onSave (values)
showToast ( 'Saved!' , { color: 'green' })
} catch (error) {
showToast ( 'Failed to save' , { color: 'red' })
}
}
return (
< form onSubmit = {handleSubmit}>
< TextInput
label = "Title"
value = {values.title}
onChange = {( e ) => setValues ({ ... values, title: e.target.value })}
error = {errors.title}
/>
< Textarea
label = "Content"
value = {values.content}
onChange = {( e ) => setValues ({ ... values, content: e.target.value })}
error = {errors.content}
/>
< Button type = "submit" >Save</ Button >
</ form >
)
}
Remember : Quality over speed. Take time to write maintainable, secure, and user-friendly code.
Last modified on April 29, 2026