Debugging and Performance
Debugging your App
The primary tools for debugging your app are Chrome DevTools (for DOM/console/network) and command-line logging.
Using Chrome DevTools
For detailed information on connecting Chrome DevTools to your WebF app, see the Development Workflow section.
With Chrome DevTools connected, you can:
- Console: View
console.log(),console.error(), and other console messages - Elements: Inspect the DOM tree, view computed styles, and modify elements in real-time
- Network: Monitor network requests, view request/response headers and payloads
- Application: Inspect localStorage, sessionStorage, and other storage APIs
Debugging Tips
Use Descriptive Console Logs
// ❌ Not helpful
console.log(data);
// ✅ Better
console.log('User data received:', data);
// ✅ Even better - use console methods
console.group('API Response');
console.log('Status:', response.status);
console.log('Data:', response.data);
console.groupEnd();Use Debugger Statements (with caveats)
While breakpoints in Chrome DevTools are not yet supported, you can use strategic console.log statements to understand code flow:
function processData(data) {
console.log('Processing data:', data);
const result = transform(data);
console.log('After transform:', result);
return result;
}Inspect Component State
For React, you can log component state and props:
function MyComponent({ user }) {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('MyComponent state:', { count, user });
}, [count, user]);
return <div>...</div>;
}Common Issues and Solutions
Elements Not Measuring Correctly
Problem: getBoundingClientRect() returns zeros or incorrect values.
Solution: Use the onscreen event or useFlutterAttached hook to ensure the element is rendered before measuring.
import { useFlutterAttached } from '@openwebf/react-core-ui';
function MyComponent() {
const ref = useFlutterAttached(() => {
// Safe to measure here
const rect = ref.current.getBoundingClientRect();
console.log('Element size:', rect.width, rect.height);
});
return <div ref={ref}>Content</div>;
}Network Requests Failing
Problem: Fetch requests fail or return CORS errors.
Solutions:
- Check that your API server is running
- Ensure CORS headers are properly configured on your backend
- Verify the URL is correct
- Check network logs in Chrome DevTools
// Add error handling
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.catch(error => {
console.error('Fetch error:', error);
});Performance Profiling
Performance Monitoring in Code
You can also add custom performance markers in your code:
// Mark the start of an operation
performance.mark('data-fetch-start');
await fetch('https://api.example.com/data')
.then(r => r.json())
.then(data => {
// Mark the end
performance.mark('data-fetch-end');
// Measure the duration
performance.measure(
'data-fetch',
'data-fetch-start',
'data-fetch-end'
);
const measure = performance.getEntriesByName('data-fetch')[0];
console.log(`Data fetch took ${measure.duration}ms`);
});Best Practices
1. Leverage Async Rendering
WebF’s async rendering model automatically batches DOM updates for better performance. DOM mutations in WebF are 20x cheaper than in browsers because updates are batched and processed in the next frame.
// ✅ Multiple DOM updates are automatically batched
function updateUI(items) {
items.forEach(item => {
const element = document.createElement('div');
element.textContent = item.name;
container.appendChild(element); // All appends batched automatically
});
}Note: Unlike browsers, you don’t need DocumentFragment optimization in WebF - the async rendering model handles batching for you.
For very long lists (hundreds or thousands of items), use WebFListView for virtualization and optimal rendering performance.
2. Use the onscreen Event
Defer expensive operations until elements are actually rendered:
import { useFlutterAttached } from '@openwebf/react-core-ui';
function LazyImage({ src }) {
const ref = useFlutterAttached(() => {
// Load image only when element is rendered
ref.current.src = src;
});
return <img ref={ref} alt="" />;
}3. Use Native Components for Performance-Critical UI
For complex animations, large lists, or performance-sensitive UI, prefer Flutter widgets over pure web implementations:
import { FlutterCupertinoButton } from '@openwebf/react-cupertino-ui';
// ✅ High-performance native button with perfect animations
function MyButton() {
return <FlutterCupertinoButton onClick={handleClick}>Click</FlutterCupertinoButton>;
}4. Optimize Images and Assets
- Use appropriate image formats (WebP for photos, SVG for icons)
- Compress images before including them
- Use lazy loading for images below the fold
- Consider using native image components for better performance
// ✅ Simple lazy loading with native loading attribute
function ProductImage({ src, alt }) {
return <img src={src} alt={alt} loading="lazy" />;
}
// ✅ Lazy loading in HTML
<img src="/path/to/image.jpg" alt="Product" loading="lazy" />The loading="lazy" attribute is the recommended approach for lazy loading images in WebF.
5. Minimize Native Binding Calls
While the Native Binding System is optimized, be mindful of:
// ❌ Bad - frequent small calls
for (let i = 0; i < 1000; i++) {
nativePlugin.saveItem(items[i]);
}
// ✅ Good - batch operations
nativePlugin.saveItems(items);6. Use CSS Transforms for Animations
CSS transforms are highly optimized and run on the compositor thread:
/* ✅ Good - hardware accelerated */
.animated {
transition: transform 0.3s;
}
.animated:hover {
transform: scale(1.1);
}
/* ⚠️ Less efficient - causes layout recalculation */
.animated {
transition: width 0.3s;
}7. Debounce Expensive Operations
For operations triggered by user input, use debouncing:
import { useState, useEffect } from 'react';
function SearchComponent() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
// Debounce search
const timer = setTimeout(() => {
if (query) {
performSearch(query).then(setResults);
}
}, 300);
return () => clearTimeout(timer);
}, [query]);
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<ResultsList results={results} />
</div>
);
}8. Monitor Bundle Size
Keep your JavaScript bundle size small:
# Analyze bundle size with Vite
npm run build -- --mode analyze
# Check what's in your bundle
npx vite-bundle-visualizer9. Code Splitting
Split your code into smaller chunks that load on demand:
import { lazy, Suspense } from 'react';
// Lazy load components
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}10. Use Production Builds for Testing
Always test performance with production builds:
npm run build
npm run previewDevelopment builds include extra code for debugging and hot reloading that significantly impacts performance.
Performance Checklist
- App maintains 60 FPS during normal usage
- No layout thrashing (rapid read/write of DOM properties)
- Images are optimized and lazy-loaded
- Bundle size is reasonable (< 500KB for initial load)
- Code splitting is implemented for large apps
- Expensive operations are debounced or throttled
- Native components are used for performance-critical UI
- CSS transforms are used for animations
- Production build is tested before release
- Network requests are cached when appropriate