DEV Community

Jaji
Jaji

Posted on

A Framework for UI Component Architecture: Lessons from a File Explorer

When faced with a complex UI component like a file explorer, many developers struggle with where to start. What seemed like a straightforward task—displaying files and folders with expand/collapse functionality—quickly becomes overwhelming without a systematic approach.

In this article, I'll share a powerful framework for architecting UI components, using a file explorer as our case study. By breaking down the problem methodically, we can transform complexity into a clear, maintainable solution.

The Architectural Framework

1. Domain Modeling

Begin by understanding what you're representing:

  • What are the core entities?
  • How do they relate to each other?
  • What data structures best represent them?

File Explorer Example:

type FileSystemNode = {
  id: number;
  name: string;
  children?: FileSystemNode[]; // Present for directories, absent for files
}
Enter fullscreen mode Exit fullscreen mode

This recursive type elegantly captures both files (leaf nodes without children) and directories (nodes with children). By recognizing this pattern early, we lay a foundation for our component design.

2. Component Decomposition

Identify distinct responsibilities in your UI:

  • What are the logical separation points?
  • Which parts have different responsibilities?
  • How do these parts relate to each other?

File Explorer Example:

  • FileExplorer: Container component that initializes the explorer
  • FileList: List processing component that handles organizing items
  • FileObject: Individual item component that renders files/directories

Each component has a clear, single responsibility, making the code more maintainable and easier to reason about.

3. Data Flow Planning

Map how data moves through your components:

  • Where does data enter the system?
  • What transformations does it undergo?
  • Where does recursion occur (if applicable)?

File Explorer Example:

  1. Root data enters via FileExplorer
  2. FileList processes the data:
    • Separates directories from files
    • Sorts each group alphabetically
    • Combines with directories first
  3. Individual items are rendered via FileObject
  4. When a directory is expanded, FileObject recursively renders another FileList

This clear data flow makes the system's behavior predictable and debuggable.

4. State Management Strategy

Determine what state you need and where it belongs:

  • What state is required?
  • Where should each piece of state live?
  • How do state changes propagate?

File Explorer Example:

  • Expanded/collapsed state for directories is managed locally in each FileObject
  • State changes (toggling expanded state) are handled via click handlers on directories
  • No global state is needed, as each directory manages its own expanded/collapsed state

This local state approach keeps the component clean and avoids unnecessary complexity.

5. Interaction Design

Plan how users will interact with your components:

  • What actions can users take?
  • How does the UI respond to these actions?
  • How does the UI communicate its current state?

File Explorer Example:

  • Users click on directories to expand/collapse them
  • Expanded directories show their contents; collapsed ones hide them
  • Visual indicators (like +/- symbols) show the current state of each directory

The File Explorer Implementation

Putting it all together, our architecture leads to this implementation:

// FileExplorer.jsx - The container component
function FileExplorer({ data }) {
  return (
    <div>
      <FileList fileList={data} level={1} />
    </div>
  );
}

// FileList.jsx - The list processing component
function FileList({ fileList, level }) {
  // Process: separate directories and files
  const directories = fileList.filter(item => item.children);
  const files = fileList.filter(item => !item.children);

  // Sort each group alphabetically
  directories.sort((a, b) => a.name.localeCompare(b.name));
  files.sort((a, b) => a.name.localeCompare(b.name));

  // Combine with directories first
  const items = [...directories, ...files];

  return (
    <ul className="file-list">
      {items.map(item => (
        <FileObject key={item.id} file={item} level={level} />
      ))}
    </ul>
  );
}

// FileObject.jsx - The individual item component
function FileObject({ file, level }) {
  const [expanded, setExpanded] = useState(false);
  const isDirectory = Boolean(file.children);

  return (
    <li>
      <div 
        onClick={() => isDirectory && setExpanded(!expanded)}
        style={{ paddingLeft: `${level * 10}px` }}
      >
        {file.name} {isDirectory && (expanded ? '[-]' : '[+]')}
      </div>

      {isDirectory && expanded && (
        <FileList fileList={file.children} level={level + 1} />
      )}
    </li>
  );
}
Enter fullscreen mode Exit fullscreen mode

Benefits of This Approach

By following this framework:

  1. Clarity: Each component has a clear purpose
  2. Maintainability: Changes to one aspect won't break others
  3. Scalability: The solution can handle complex hierarchies
  4. Reusability: Components can be reused in other contexts
  5. Testability: Components can be tested in isolation

Beyond File Explorers

This framework applies to many UI challenges:

  • Comment threads with nested replies
  • Organization charts
  • Menu systems with submenus
  • Category hierarchies in e-commerce
  • Document outlines with collapsible sections

Any time you encounter a problem with hierarchical data and interactive UI elements, this framework can guide your architecture.

Conclusion

Architecting UI components requires more than just coding skills—it demands a structured approach to problem-solving. By following the framework outlined here, you can transform complex requirements into elegant, maintainable solutions.

The next time you're faced with a challenging UI component, try breaking it down using these five steps:

  1. Domain Modeling
  2. Component Decomposition
  3. Data Flow Planning
  4. State Management Strategy
  5. Interaction Design

Your future self (and colleagues) will thank you for the clear, well-structured code that results.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay