2024-08-24 by Roel Kristelijn
When building React applications, it's easy to fall into the trap of over-engineering or writing components that violate basic software principles. The Bare Minimum Principles provide a practical framework for writing code that's clear, maintainable, and follows established patterns.
Let's explore how these principles apply to React component development through a real-world example.
Follow the framework's idioms and conventions
React and its ecosystem have established patterns. Fighting against them creates friction and confusion.
1// ❌ Fighting React patterns 2class MyComponent extends Component { 3 constructor(props) { 4 super(props); 5 this.state = { count: 0 }; 6 } 7 8 componentDidMount() { 9 // Complex lifecycle logic 10 } 11} 12 13// ✅ Following React idioms 14function MyComponent() { 15 const [count, setCount] = useState(0); 16 17 useEffect(() => { 18 // Clear, declarative effects 19 }, []); 20}
Write code that reads like mundane English
80% of programming is reading code. Make it easy.
1// ❌ Unclear intent 2const data = items.filter(x => x.status === 'active' && x.type === 'premium'); 3 4// ✅ Clear intent 5const activePremiumUsers = users.filter(user => 6 user.isActive && user.isPremium 7);
Simplicity is clarity by design
Don't build abstractions until you need them.
1// ❌ Over-engineered 2const ConfigurableButton = ({ variant, size, color, theme, ...props }) => { 3 const computedStyles = useMemo(() => 4 generateDynamicStyles(variant, size, color, theme), [variant, size, color, theme] 5 ); 6 return <button style={computedStyles} {...props} />; 7}; 8 9// ✅ Simple and clear 10const PrimaryButton = ({ children, onClick }) => ( 11 <button className="btn-primary" onClick={onClick}> 12 {children} 13 </button> 14);
Don't build it until it's needed
Avoid speculative features and abstractions.
Encapsulate complexity behind clear interfaces
1// ❌ Implementation details exposed 2function UserProfile({ user }) { 3 const isVip = user.subscriptionTier === 'premium' && 4 user.accountAge > 365 && 5 user.totalSpent > 1000; 6 7 return <div>{isVip && <VipBadge />}</div>; 8} 9 10// ✅ Implementation hidden 11function UserProfile({ user }) { 12 return <div>{user.isVip() && <VipBadge />}</div>; 13}
Names should reflect business intent, not implementation
1// ❌ Implementation-focused names 2const data = fetchData(); 3const isValid = check(input); 4 5// ✅ Intent-focused names 6const userProfiles = fetchUserProfiles(); 7const isEmailValid = validateEmailFormat(email);
Let's analyze a real React component from our blog application:
1export default function PostContent({ post }: PostContentProps) { 2 const theme = useTheme(); 3 const isDarkMode = theme.palette.mode === 'dark'; 4 5 return ( 6 <Box sx={{ mb: 4 }}> 7 <Typography variant="h3" component="h1" gutterBottom> 8 {post.title} 9 </Typography> 10 11 <Box sx={{ 12 '& h1': { fontSize: '2rem', fontWeight: 700, mb: 2, mt: 3 }, 13 '& h2': { fontSize: '1.75rem', fontWeight: 600, mb: 1.5, mt: 2.5 }, 14 // ... 60+ lines of CSS-in-JS 15 }}> 16 <ReactMarkdown 17 components={{ 18 code(props) { 19 const { className, children, ...rest } = props; 20 const match = /language-(\w+)/.exec(className || ''); 21 const language = match ? match[1] : ''; 22 23 if (language === 'mermaid') { 24 return <Mermaid chart={String(children).replace(/\n$/, '')} />; 25 } 26 27 if (match) { 28 return ( 29 <SyntaxHighlighter 30 style={isDarkMode ? oneDark : oneLight} 31 language={language} 32 // ... complex configuration 33 > 34 {String(children).replace(/\n$/, '')} 35 </SyntaxHighlighter> 36 ); 37 } 38 39 return <code className={className} {...rest}>{children}</code>; 40 }, 41 }} 42 > 43 {post.content} 44 </ReactMarkdown> 45 </Box> 46 </Box> 47 ); 48}
Issue: Large sx prop instead of using MUI's styling patterns
1// Not idiomatic MUI 2<Box sx={{ 3 '& h1': { fontSize: '2rem', ... }, 4 '& h2': { fontSize: '1.75rem', ... }, 5 // 60+ lines 6}}>
Better: Use MUI's theme system and styled components
1const useMarkdownStyles = () => { 2 const theme = useTheme(); 3 return { 4 h1: theme.typography.h1, 5 h2: theme.typography.h2, 6 // Theme-based styling 7 }; 8};
Issue: Component is too long (130+ lines) and mixes concerns
The component handles:
Issue: Implementation details leak into the component
1// Implementation details exposed 2const match = /language-(\w+)/.exec(className || ''); 3const isDarkMode = theme.palette.mode === 'dark';
Better: Hide complexity behind clear interfaces
1// Hide implementation 2const codeLanguage = extractLanguageFromClassName(className); 3const syntaxTheme = getSyntaxHighlighterTheme();
Issue: Generic, unclear names
1const match = /language-(\w+)/.exec(className || ''); // What does it match? 2const { className, children, ...rest } = props; // What's in rest?
Better: Intention-revealing names
1const languageMatch = /language-(\w+)/.exec(className || ''); 2const { className, children, ...otherCodeProps } = props;
Following the bare minimum principles, here's how we can improve this component:
1// Hide implementation behind clear interface 2function CodeBlock({ className, children, ...props }) { 3 const codeLanguage = extractLanguageFromClassName(className); 4 5 if (isMermaidDiagram(codeLanguage)) { 6 return <MermaidDiagram content={children} />; 7 } 8 9 if (hasLanguage(codeLanguage)) { 10 return <SyntaxHighlightedCode language={codeLanguage}>{children}</SyntaxHighlightedCode>; 11 } 12 13 return <InlineCode className={className} {...props}>{children}</InlineCode>; 14}
1// Use MUI patterns instead of large sx prop 2const markdownStyles = { 3 h1: { fontSize: '2rem', fontWeight: 700, mb: 2, mt: 3 }, 4 h2: { fontSize: '1.75rem', fontWeight: 600, mb: 1.5, mt: 2.5 }, 5 // Clear, focused styling 6};
1export default function PostContent({ post }) { 2 return ( 3 <article> 4 <PostHeader title={post.title} date={post.date} author={post.author} /> 5 <MarkdownContent content={post.content} /> 6 </article> 7 ); 8}
These principles aren't rigid laws - they're guidelines that prevent common pitfalls. As you gain experience, you'll learn when breaking them serves a greater purpose. But until then, following these principles will save you from future pain and make your code more maintainable.
The best engineers aren't those who never break the rules, but those who understand when and why to break them.
Next Steps: In our next post, we'll implement this refactoring step by step, showing how each principle improves code clarity and maintainability.