[HN Gopher] Delightful React file/directory structure
       ___________________________________________________________________
        
       Delightful React file/directory structure
        
       Author : joshwcomeau
       Score  : 82 points
       Date   : 2022-03-15 13:11 UTC (9 hours ago)
        
 (HTM) web link (www.joshwcomeau.com)
 (TXT) w3m dump (www.joshwcomeau.com)
        
       | smotched wrote:
       | How are you adding the slight bounce animation on your file
       | explorer? its pretty neat.
        
         | ihuman wrote:
         | The file exporer's source code is at the bottom of the page. It
         | looks like it's animated using framer-motion.
        
       | neurotrace wrote:
       | > 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
       | ;)
        
       | bayesian_horse wrote:
       | 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.
        
       | azemetre wrote:
       | 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.
        
         | joshwcomeau wrote:
         | I agree! I'd do `Button.test.tsx` right inside the component
         | directory. Managing a mirrored structure feels like a lot of
         | wasted effort. Same for Storybook stories (`Button.story.tsx`).
         | 
         | That said, I'm not 100% sold on the value of testing React
         | components, outside of some very specific cases (eg. a very
         | complex component with lots of internal state). I prefer to
         | write integration / e2e tests that check views/pages/flows as a
         | whole. And so for those, since they aren't connected to
         | specific components, I do have a separate `/tests` folder.
        
         | notapenny wrote:
         | If they're using something like create-react-app, it may force
         | that structure on them. I remember in earlier versions it was
         | harder to configure other folders to put tests in. I prefer
         | your ideal structure as well, if you're going to co-locate
         | files relating to Button, do it properly, makes it much easier
         | to find things and in this case see if there are tests for your
         | component.
        
           | azemetre wrote:
           | hmm. I honestly never used CRA outside of an interview
           | assessment and don't really remember much. Early on in my web
           | career I was encouraged to create my own scaffolding tools,
           | that practice just always stuck with me.
           | 
           | Kinda curious to learn how they enforced directory
           | structures. I know now they co-locate tests, but IIRC CRA
           | always used jest and with jest you just set the globs you
           | want to use in the config file. Hardly hardcoded or strictly
           | enforced, but I could be wrong.
        
             | notapenny wrote:
             | Could also be that I'm remembering it wrong, but I recall
             | having some issues with CRA and structuring tests at some
             | point. In any case, CRA sets up a separate src and test
             | folder, so I guess a lot of people just think they should
             | structure their tests that way.
        
         | throwaway858 wrote:
         | There are several good reasons to keep your test code in a
         | completely separate directory (or "project")
         | 
         | 1. Sometimes you want to do some experimental refactor in your
         | application. This might break a lot of tests (cause them to not
         | compile). But you first want to play around with the new change
         | before committing to it and updating all your tests. If your
         | test code is in the same project then the compiler errors will
         | prevent you from doing this.
         | 
         | 2. Your test code and application code usually need different
         | dependencies. You don't want your test code to accidentally
         | call functions from some helper library, and you don't want
         | your application code accidentally calling functions from some
         | test framework. If you have a shared list of dependencies and a
         | large team then this will inevitably happen.
         | 
         | 3. You don't want your application code to accidentally call
         | helper functions from your test files. If you mix them in the
         | same project then with a large team this will inevitably
         | happen.
         | 
         | 4. For code navigation and things like IDE "find usages", it is
         | better to not be flooded with results from test code, in order
         | to be able to focus on discovering how the code works.
         | (Sometimes you do want to be taken to the test code which is
         | why good IDEs allow you to choose to toggle on/off cross-
         | project navigation).
         | 
         | 5. Bonus: Sometimes you may want to write your test code in a
         | different programming language then your application. This is
         | uncommon but does happen. For example a C library with a test
         | suite written in C++. Or a webapp backend with tests written in
         | a scripting language using selenium. In these cases you have to
         | have a separate project, and so for consistency you do it as
         | well also for tests that use the same programming language.
         | 
         | I think the last point is actually the most important: it helps
         | formulate the understanding that your test suite should be
         | viewed as its own separate and independent program, not
         | inherently tied to the library code that you are developing.
         | This leads to two insights: 1) there's no reason why you
         | couldn't have more than one test suite to test your library
         | (possibly developed by different teams). 2) more interestingly:
         | you should be able to take your test suite, and run it against
         | a different implementation of your library. This makes sense
         | for something like a test suite for a filesystem or SQL
         | database. But even for your custom library, if you ever need to
         | do a rewrite, or port to a different platform/language, then
         | being able to take your existing test suite with you will be
         | invaluable.
        
         | d357r0y3r wrote:
         | I think people do the "separate directory for tests" thing
         | because test runners have, in the past, shipped with a default
         | configuration to target a test directory, rather than match
         | test files by suffix.
         | 
         | Colocation of test files is the hands down winner and
         | encourages the writing of tests. When you're making a change to
         | a code file, you probably won't think to scour the codebase for
         | relevant tests. If you see the test file right next to source
         | file in your editor, you probably will.
        
           | zelphirkalt wrote:
           | When you make a change to a code file, you should run your
           | tests. Why else do you have them? A test failure should
           | result in you going to fix that or, if appropriate, change
           | assumptions of the test. I don't see how it is harder to add
           | tests either.
        
             | Jernik wrote:
             | What about changes that don't cause test failures? A really
             | quick one I can think of is a pure addition. Lets say you
             | have tests for Feature A in 3 places. You add a thing to
             | Feature A, and find 2 places where Feature A is tested, add
             | tests to those, feel like you did your due diligence and
             | move on. Co-located tests would fix this problem
        
             | azemetre wrote:
             | It's not that it's impossible to test, it's just more of
             | "out of sight, out of mind." Couple this with a poor
             | engineering culture in general, it's easier for me to
             | understand how this pattern encourages very poor testing.
        
         | gherkinnn wrote:
         | That's exactly what Deno does [0] in its standard library.
         | Colocate tests with code. It just works.
         | 
         | Some people also like to go further the bad way and group code
         | by type rather than feature.                   src/
         | components/                 componentA.tsx
         | componentB.tsx             hooks/                 hookA.ts
         | hookB.ts             tests/                 componentA.test.tsx
         | componentB.test.tsx
         | 
         | Angular pre 1.5 liked this a lot. Must be a Java thing.
         | 
         | https://deno.land/std@0.129.0/collections
        
         | striking wrote:
         | Workplace puts tests in a __tests__ folder next to the thing
         | under test (e.g. `Button/Button.tsx` is tested by
         | `Button/__tests__/Button.test.tsx`).
        
       | mind-blight wrote:
       | 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.
        
       | throwaway284534 wrote:
       | 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.
        
         | keb_ wrote:
         | I used to do this as a newb before I learned about how index.js
         | works. In hindsight, it makes a lot of sense (especially when
         | you consider browser parity), and I find it amusing how common
         | the use of index.js is in React codebases, when Ryan Dahl named
         | index.js one of his greatest mistakes when creating Node.
        
       | efrafa wrote:
       | I would avoid default exports at all cost.
        
         | shmde wrote:
         | All the tutorials which I have been following always default
         | export it. Whats the reason to avoid default export ?
        
           | neurotrace wrote:
           | Not OP but I avoid default exports unless I need to do a lazy
           | import. For me, there's a few reasons: consistent naming,
           | easier importing, encourages importing what you need, avoids
           | weird situation where you do both a default and named import.
           | 
           | Consistent naming: since I named the exported thing, that's
           | what the consumers will call it as well unless they go out of
           | their way to do `import { X as Y } from '...'`. This is
           | useful because it helps ensure all consuming code looks
           | similar at least in it's usage of modules. More familiar code
           | is easier to read and reason about. It's also useful for
           | looking up usages of something. I know I can run $IDE's
           | version of "Find Usage" but sometimes it's easier to just
           | Ctrl+Shift+F > X.
           | 
           | Easier importing: If I have something exported as X then I go
           | to a module that isn't using it and I type X, my editor will
           | suggest I import it from the appropriate module. This _can_
           | work on default exports but only if you use the same name as
           | was used internally at the point of definition. That kind of
           | defeats the benefit of default imports where you can use
           | whatever name you want without hassle and it's your
           | responsibility to make sure you match the names correctly.
           | 
           | Encourages importing what you need: when you default to named
           | exports, you default to pulling in the bare minimum to do the
           | job. Consider the opposite case. Someone imports some
           | monolithic chunk of code as a single object then does
           | `library.thingIWant` with a bunch of different things. Now
           | you've got a larger possible space to look at when trying to
           | load all of the context of a file in to your head. This is
           | also useful for tree shaking. Assuming you've written your
           | code in a well-defined manner and are using a smart build
           | system, it can more easily eliminate dead code because it
           | knows you never import certain pieces of a module. This
           | applies to both your code and code from third-parties.
           | 
           | Both default and named imports: This is common when working
           | with React. You'll see `import React, { useState } from
           | 'react'` or similar. I don't have a rational answer for this
           | but it rubs me the wrong way.
        
         | wjmao88 wrote:
         | If your whole file is semantically the default export, then why
         | not?
         | 
         | There are advantages with default exports, e.g. you can name it
         | at usage site the way you want without the `:` syntax. Also
         | some things like `React.lazy` only works with default exports.
        
       | lawwantsin17 wrote:
        
       | zheksoon wrote:
       | 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
        
         | kakuri wrote:
         | I have a bit of a preference for this approach as well, but
         | having worked on numerous large projects I have to agree with
         | Josh's arguments against it. Maybe on a small personal project
         | it can succeed but on large ongoing projects I favor organizing
         | by function rather than feature.
        
       | lazrgatr wrote:
       | Unrelated to the post, but the site has the creepiest pop-up I've
       | ever seen. It scared me a bit.
        
       | tomc1985 wrote:
       | 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
        
         | politelemon wrote:
         | Whenever I see the word 'beautiful', I often find that thing is
         | not beautiful. But it gets used, a _lot_. The only explanation
         | I can come up with, is they are simply assuming that you are
         | incapable of forming your own thoughts, and need to be told
         | what to feel.
        
         | tshaddox wrote:
         | Are there useful technical qualities that don't reduce to
         | feelings?
        
         | striking wrote:
         | "Clean" is used in this blog post.
         | 
         | I think it is kind of weird to ask folks not to use feelings-
         | based words when the point here is that they are describing
         | their opinions.
         | 
         | When Josh writes
         | 
         | > Well, there is no one "right" way, but I've tried lots of
         | different approaches in the 7+ years I've been using React, and
         | I've iterated my way to a solution I'm really happy with.
         | 
         | how would you prefer he have phrased it? The iteration has
         | ended in joy for him. Should he not express his joy? Sure, he
         | could express in a technically-phrased way why it causes him
         | joy (maybe tighter iteration loops, maybe easier generation of
         | value, etc. etc.) but then he'd have to prove those things,
         | wouldn't he?
         | 
         | It's hard enough to just get your opinions out there without
         | also having to prove each one as if you were declaring an
         | ultimate statement of fact.
        
       | shanehoban wrote:
       | 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!
        
       | beaconstudios wrote:
       | 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.
        
         | dsmmcken wrote:
         | I think his criticism of that approach is that you end up with
         | a bunch of index.tsx tabs open in your IDE and it can be hard
         | to tell what is what.
         | 
         | My approach is similar to his, but is simplified to have one
         | index at the top of the components folder for all the
         | components:                 components          index.tsx <-
         | wrapper exports all components          mywidget.tsx
         | myotherwidget.tsx          mycomplexwidget
         | mycomplexwidget.tsx            subpart.tsx
         | 
         | Then import is easy, and feels less redundant
         | import { mywidget, mycomplexwidget } from '@foo/components'
        
           | beaconstudios wrote:
           | That issue with too many index.ts files used to be a major
           | issue but nowadays I use VS Code which disambiguates matching
           | filenames with their parent directory.
           | 
           | I think I just don't really like using re-exporting index
           | files if I can avoid them, because I don't see much of a
           | positive cost/benefit in most cases. In your example I'd be
           | doing something like this instead:                   import {
           | mywidget } from '@foo/components/mywidget';         import {
           | mycomplexwidget } from '@foo/components/mycomplexwidget';
           | 
           | I could fish for pseudo-objective arguments for this but
           | honestly I think it's mostly just personal preference!
        
             | deanc wrote:
             | > That issue with too many index.ts files used to be a
             | major issue but nowadays I use VS Code which disambiguates
             | matching filenames with their parent directory.
             | 
             | Any source on that. I'm in vscode right now with several
             | index.tsx files open and no disambiguation in the tabs.
        
               | beaconstudios wrote:
               | here's a screenshot from my VS Code right now:
               | https://imgur.com/a/kTbBjJP
               | 
               | it's controlled by the "workbench.editor.labelFormat"
               | setting, but the default will disambiguate when two files
               | share a name.
        
       | tomphoolery wrote:
        
         | recursive wrote:
         | Thanks.
        
       | ldd wrote:
       | > 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.
        
       | notapenny wrote:
       | 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.
        
       ___________________________________________________________________
       (page generated 2022-03-15 23:01 UTC)