Delightful React file/directory structure

  • Kinda wish Josh would mention how he structures his tests in his projects. Something I'm currently struggling at work is that within our code repos, the pattern that everyone seems to copy is mimic the src/ directory for the test/ directory, rather than co-locating tests along with components. This means a structure for components:

        src/
          components/
            Button/
              Button.tsx
    
    Is just copied ad-hoc for tests, so we get this:

        test/
          components/
            Button/
              Button.test.tsx
    
    Ideally it would be structured as so:

        src/
          components/
            Button/
              Button.tsx
              Button.test.tsx
    
    This pattern makes it extremely taxing to utilize codemods in the future to transform repos into something else. It also, in my opinion, instills thinking that testing is separate from feature development and is often treated as such. As a result, my org often forgoes proper testing or tacking it on at the very end to just fulfill the motions of the dev cycle.

    The oddest part is that some staff and principle engineers are very adamant about this structure. It's just additional boilerplate that doesn't help and makes it hard to understand what components have tests (especially when you get in the weeds of not having a flat component directory, where child components have nested directories often several layers deep). I have no idea where this pattern permeates but it should be discouraged in most frontend projects.

    Co-location of files should absolutely be encouraged, the alternative is just a jumble of directories that make it hard to grok what is actually happening.

    IDK, just ranting now at this point.

  • Keep it as flat as possible until you really, really need to structure it.

    A folder for components and a maybe separate one for pages or containers is probably all you need (and even those you could probably stick in components until some structure arises). Have seen quite a lot of code-bases that suffered from people trying to structure their folders around a certain model too early on, change their mind, change it again, result: a mess.

  • I prefer using fractal-style component structure, avoiding folders like `components` for single-use components. It looks like this:

      src/
        App/
          screens/
            Login/
              component.tsx
              index.tsx
              model.tsx
              styles.scss
            Dashboard/
              SomeDashboardPart/
                component.tsx
                index.tsx
              SomeOtherDashboardPart/
                component.tsx
                index.tsx
              helpers/
                someHelper.tsx
              component.tsx
              index.tsx
              model.tsx
        components/
          Button/
            component.tsx
            index.tsx
    
    `model.tsx` files here are MobX data models used for this specific component

  • There are some things I really like about this, and some things that can shoot you in the foot.

    Like:

    - Having more than one component in a file. A lot of complex components can be made simpler by breaking then into many smaller, temper components. A lot of these helper components are too specific to warrant generalized use. Adding a new file for each one (especially if they're only 3-5 lines of code) clutters the code base, so keeping them in the same time where they're used makes sense. You can always pull them into a separate file later.

    - Not making everything "index.js". That really makes it more difficult to keep track of what's what code is where.

    Dislike:

    - using indeed.js for experts. This makes it easier to accidentally add circular imports, and much harder to track them down.

    - putting all components in a components/ directory. This is fine for smaller apps, but it starts to get unweildy once you add more features, teams, or team members to the repo. This is especially true if you use redux, Apollo, and React-router (or any of their competing libraries). Selectors, reducers, GraphQL queries, get spread across the code base, and it becomes difficult to teach down which component rely on what selectors (for example). This starts to bog down onboarding and cross team collaboration since ownership becomes more difficult to define. You end up with spaghetti code pretty easily.

  • > Finally, in terms of organization, I want things to be organized by function, not by feature.

    In my experience, writing a game in mostly React, separating things by feature is more intuitive down the line. Mostly because after some time has passed, finding something is pretty easy and I don't get completely lost in code. And yes, this means having custom hooks in the same file as the actual component sometimes.

    I also purposefully let convoluted import statements with lots of '../../../' just exist because my IDE (VS Code) takes care of it and if you really think about it, you never really spend too much time on them.

    Then again, I've come to the realization that different things work for different folks. And different projects. If anything, I just encourage more people to try different structures until something 'clicks' with you.

  • I do something similar, except I directly use index.tsx as the widget in a directory structure, like this:

        components
          Button.tsx
          Menu
            index.tsx <-- menu component
            Link.tsx <-- "Link" component only used within Menu
    
    That way if I want to break out the Button component into multiple sub-components, I don't need to change the imports, but I also don't need to add a re-exporting index.ts in every single folder (it gets annoying once you're up to dozens of folders).

    Also, for funsies I have both a components directory for reusable components and a separate "app" directory for the core containers that make up the first-class functionality. index.tsx in that folder is what would normally be the App.tsx, so the JSX nesting structure of the app is mirrored in the directory structure:

        app
          index.tsx <-- entrypoint
          Login.tsx
          Register.tsx
          routes
            index.tsx <-- core router
              Home.tsx
              
    etc... This also makes SSR easy because then in the root 'src' directory that contains all this I can have a server.tsx with ReactDOM.render and a client.tsx with ReactDOM.hydrate, and each can use their respective isomorphic context providers.

    Also I don't know if this is standard nowadays but aliasing "src" to the source root and adding it as a baseDir in tsconfig.json means you can use absolute imports.

  • > Finally, in terms of organization, I want things to be organized by function, not by feature.

    I found this super surprising. I much prefer doing things by feature than by function. Where it goes is a function of which page/view/route it's on. If it's a general purpose component that is used on multiple pages (like `Button`) then, sure, it goes in `src/components`. Otherwise, it goes in `src/routes/app-section/components`. Truth be told, I've taken to doing a setup like this:

        src/
        - routes/
          - app-section/
            - effects/
              - some-action.effect.ts
            - ui/
              - app-section.component.tsx
              - index.ts
              - some-component.component.tsx
            - app-section.route.ts
            - app-section.types.ts
            - index.ts
            
    `effects` basically holds your business logic, `ui` contains section specific components and exports the top-level component via `ui/index.ts`, `thing.route` hooks up the route to state management, `index.ts` provides a bundle for hooking up the effects to your effect system and the route component itself. The `name.type.extension` naming scheme clears up the tab name confusion problem. Maybe I should write my own article about this ;)

  • Can we please stop describing technical things using feelings-based words? It's foppish, it makes tech sound really really disingenuous and fake, and it cheapens the words as well.

    "Clean," "well-organized," "atomic" ... all adjectives, all descriptive, zero emotion

  • When it comes to the index.js issue, I’m partial to “unwrapping” component directories. For example, if a Post component has a few one off sub-components, they’re placed in Post subdirectory:

        src/
          components/
            Post.jsx
            Post/
              PostHeader.jsx 
              PostActions.jsx
    
    Then import as:

        import Post from “components/Post.js”
    
    And within the component:

        import PostActions from “components/Post/PostAction.js”
    
    I’ve felt this approach eliminates most index.js reexports and aligns closer with a browser’s native import syntax. This all comes at the cost of less encapsulation, but in a private codebase, it’s less of an issue.

  • In SPAs it's pretty easy to tell to which "code feature" a component belongs. For one group of component, it depends on what page they are on. For the rest it basically doesn't matter.

    I prefer the feature based approach because it is easier to navigate.

  • Unrelated to the post, but the site has the creepiest pop-up I've ever seen. It scared me a bit.

  • I cannot get over the quality of this site, the subtle sounds, the design, the interactions, just everything. Josh is a damn genious, from one FE dev to another, bravo!

  • How are you adding the slight bounce animation on your file explorer? its pretty neat.

  • I would avoid default exports at all cost.

  • undefined

  • undefined