8 ms·
Delightful React file/directory structure
- beaconstudios 5y agoI 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 5y agoI 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 5y agoThat 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 5y ago> 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 5y agohere's a screenshot from my VS Code right now: https://imgur.com/a/kTbBjJP 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.
- bayesian_horse 5y agoIn 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 5y agoKinda 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.
- d357r0y3r 5y agoI 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 5y agoWhen 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.
- lawwantsin17 5y ago
- shanehoban 5y agoI 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!
- smotched 5y agoHow are you adding the slight bounce animation on your file explorer? its pretty neat.
- ihuman 5y agoThe file exporer's source code is at the bottom of the page. It looks like it's animated using framer-motion.
- efrafa 5y agoI would avoid default exports at all cost.
- shmde 5y agoAll the tutorials which I have been following always default export it. Whats the reason to avoid default export ?
- neurotrace 5y agoNot 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.
- throwaway284534 5y agoWhen 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.
- toastercat 5y agoI 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.
- eurasiantiger 5y agoIt is a mistake. Index.js re-exports cannot be tree-shaken by bundlers unless the module is explicitly declared free of side effects. If you import { foo } you actually import everything. Check your bundle.
- ldd 5y ago> 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.
- zheksoon 5y agoI 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 5y agoI 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.
- bern4444 5y agoAh yes, so if I have three component.tsx files open in different tabs its impossible to tell which one is which
- lazrgatr 5y agoUnrelated to the post, but the site has the creepiest pop-up I've ever seen. It scared me a bit.
- tomphoolery 5y ago
- recursive 5y agoThanks.
- notapenny 5y agoKeep 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.
- pests 5y agoI agree. I'll make a new folder when I see a pattern and I don't hesitate to move things around if I realize something's not working. I find a similar folder structure develops across my apps over time but it really depends on which libraries and the app. Usually a separate folder for high level screens. A components folder. Models and services get their own folder. State management too sometimes depending on complexity. Then the app itself might have some clearly defined sections or features that get foldered up. I heavily use the quick file open feature in vscode so file location ultimately doesn't matter. Too often people groom this kind of stuff instead of getting work done.
- neurotrace 5y ago> 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 ;)
- tomc1985 5y agoCan 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 5y agoWhenever 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.
- tomc1985 5y agoCopywriters have destroyed the meaning of "beautiful"
- striking 5y ago"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.
- tomc1985 5y agoThen can we at least stick to words with precision, and not the thesaurus drivel popularized by marketing people? The other thing is that while "delightful" might be as such to him, I have personally found that my opinions simply do not align with people who speak like this. Using that sort of vocabulary is likely to get you written off as a moron among some folks Technical writing should be simple and plain. And yes I am judging entirely by the title.
- mind-blight 5y agoThere 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.