HomeBlog Posts
Bare Minimum Principles: Writing Better React Components

Bare Minimum Principles: Writing Better React Components

2024-08-24 by Roel Kristelijn

Bare Minimum Principles: Writing Better React Components

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.

The Bare Minimum Principles

1. RTFM (Respect The Framework's Model)

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}

2. C4C (Coding For Clarity)

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);

3. KISS (Keep It Simple Stupid)

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);

4. YAGNI (You Aren't Gonna Need It)

Don't build it until it's needed

Avoid speculative features and abstractions.

5. HIPI (Hide Implementation, Present Interface)

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}

6. NBI (Naming by Intention)

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);

Real-World Example: PostContent Component Analysis

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}

Principle Violations Analysis

❌ RTFM Violation

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};

❌ C4C Violation

Issue: Component is too long (130+ lines) and mixes concerns

The component handles:

  • Post metadata rendering
  • Markdown styling configuration
  • Code block syntax highlighting
  • Theme detection
  • Mermaid diagram rendering

❌ HIPI Violation

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();

❌ NBI Violation

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;

The Refactoring Plan

Following the bare minimum principles, here's how we can improve this component:

Step 1: Extract Code Block Logic (HIPI + C4C)

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}

Step 2: Extract Styling (RTFM + KISS)

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};

Step 3: Simplify Main Component (KISS + C4C)

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}

Key Takeaways

  1. RTFM: Follow your framework's patterns - they exist for good reasons
  2. C4C: If you need to scroll to understand a component, it's too complex
  3. KISS: Start simple, add complexity only when needed
  4. HIPI: Hide messy implementation details behind clean interfaces
  5. NBI: Names should tell you what something does in business terms

Defense of Craft

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.