When I started Redux I was shocked by the number of discussions and “best practice” you could find online about it, but it didn’t take too much time to understand why: is not very opinionated about the way of structuring a project around it, and this can lead to some annoyance when you’re trying figure out what kind of structure suits better your style and your project. learning Redux In this post I’d like to share some information on my journey to achieving a comfortable Redux project structure. This is not an introduction/tutorial, a bit of knowledge of Redux is required to understand it entirely. Also, apart from redux-sagas (which can be replaced by redux-thunk or by your favorite library for handling asynchronous actions), I won’t use any external Redux library/utility. Hope you find it interesting! First stop: grouping files by “type” When I started using Redux I studied the from top to bottom and I organized my project this way: official doc This structure is promoted by the and in my opinion it is still a pretty solid option.The main drawback of a structure like is that even adding a small feature might end up in editing several different files.For example adding a field (that is updated by an action) to the product store means that you’ll have to: official Redux repository examples add the action type in types/product.js add the action creator in actions/products.js add the field in reducers/product.js And it doesn’t end here! When the app grows you’ll probably add other directories to the mix: So, after introducing in my project I relized that it was becoming too hard to maintain and I started looking for alternatives. redux-saga A different approach: grouping files by feature An alternative to the project structure above consists in grouping files by feature: This approach has been promoted by various interesting articles in the React community, and it is used in . one of the most common React boilerplate At first glance this structure seemed reasonable to me because I was encapsulating a component (the container), its state (the store) and its behavior (the actions) in a single folder, following the React’s component concept.After using it in a bigger project though I discovered that it is not all sunshine and rainbows: if you’re using Redux you’re probably doing it for sharing a slice of store across you’re app… and you can see easily that this clashes with the encapsulation concept promoted by this structure.For example dispatching an action from the product container might produce side effects on the cart container (if the cart reducer reacts in some way to the action). You must also be careful not to be caught in another conceptual trap: don’t feel forced to tie a slice of the Redux store to a container, because otherwise you’ll probably end up using Redux even when you should have opted for the simple setState. Ducks to the rescue After the small adventure of grouping by feature I went back to my initial project structure and I noticed that using Sagas for handling the asynchronous flow has the interesting side effect of turning 90% of your action creators in one-liner: const login = (email, password) => ({ type: LOGIN_REQUEST, email, password }) In a situation like this, having a dedicated file for each action creator, action type and reducer seemed a bit overkill, so I decided to try out the “Ducks” approach. is a proposal for bundling reducers, action types and actions in the same file, leading to a reduced boilerplate: Ducks The first time I adopted this syntax I fell in love with it.It's clean, it removes a lot of unnecessary boilerplate and you can easily add an action or a field to the reducer by changing a single file. Unfortunately though, using ducks started showing quickly its limits because exporting individually every single action creator and action type has some nasty side effects. In bigger containers you’ll end up having a huge list of imported actions that are only used one time (for feeding mapDispatchToProps): import { signup, login, resetPassword, logout, … } from ‘ducks/authReducer’ You won’t be able to pass to mapDispatchToProps directly all the actions of a duck. Using won’t work, because you’ll end up importing the reducer too. import * as actions You’ll waste a super precious variable name just for passing it to mapDispatchToProps.You won’t even be able do something like because you already defined the variable by assigning it to the action creator imported from the duck. const { login } = this.props login In bigger sagas you’ll end up using a lot of different actions without knowing their context (you’ll have to scroll to the top imports every time). Customizing the ducks My solution to the above issues is simple: instead of exporting individually action types and action creators I group and export them inside a types and actions object: If you structure the ducks this way you’ll be able to import the actions easily inside your components: Now you may ask: what if I need to dispatch actions of different ducks inside a component?Well, you can do something like this: I know, it’s a bit uglier, any alternative solution is welcome! Originally I opted for the name actionTypes/actionCreators instead of types/actions , but after a bit I refactored to the latter: the first option was too verbose for my taste. P.S.: From now on, for simplicity, I’ll keep referencing the files containing actions/reducers/types as “ducks”, even if in my current projects I have them in the “reducers” folder. Selectors In my opinion selectors are the most overlooked feature of Redux.I must admit that I started used them a bit too late, but reading opened my eyes and I begun viewing selectors as interfaces that expose the store to the containers. this tweet of Dan Abramov Need to display a list in a certain order? Define the selector. getProductOrderedByNameselector Need to get a specific element of a list? Define the selector. getProductById Need to filter a list? Define the selector. getExpiredProducts By following this strategy most of the selectors you define will be strongly tied to a specific reducer, so the right place for defining them is the file containing the reducer itself). Sometimes you’ll need to define more complex selectors that handle the input from different slice of the store.In this situations I put them in reducers/index.js . The sagas Sagas are really powerful and testable, but being used for managing asynchronous actions and side effects make it a bit hard to reason on how to add them to your project structure.My suggestion is to start grouping sagas that are triggered by a single redux action in the same action domain.This means that if you have a reducer that handles the authentication in , you can create , containing the sagas that are triggered by , and so on… ducks/auth.js sagas/auth.js authTypes.SIGNUP_REQUEST authTypes.LOGIN_REQUEST Sometimes thought you’ll need to trigger the same saga from with different actions. In this case you can create a more generic file containing this kind of sagas. For example this simple saga for React Native shows an alert when it intercepts an error: If the generic file (in this case i called it ) grows too much you can always refactor it later being more specific. sagas/ui.js Another thing worth nothing is that in I have a file that links the take…instruction of every saga to its implementation: sagas/index.js In this way I’m able to track down every saga easily by associating the saga with action types that trigger it. Conclusion That’s it! Here is my current project structure (as I anticipated above I renamed the “ducks” folder to “reducers”): I know there’s still room for improvements, and this is probably the main reason that made me publish this post: tips and critics are welcome! Edit #1: Where do you place actions/types that are not tied to a specific reducer? In most of my project I have one (or more) generic duck that handles this kind of actions. For example if the project is small enough I simply create , a duck file containing 1) all the actions creators and action types that are not reducer specific and 2) the state of the ui/app: ducks/app.js Please keep in mind that these ducks may not need a reducer: they can contains just action creators and action types. Edit #2 A more explicit approach Some comments on Reddit and Twitter hinted that ducks may promote the idea that the relation between actions and reducers is , when it actually is . I agree with them. In fact (in my opinion) a structure that follows the Redux philosophy even more than the one I showed you would be the following: many:1 many:many I still prefer to use the customized ducks though, and when I have actions that are not tied to a reducer (or the opposite) I adopt the solution I explained above in the edit #1.In the end you might need to test different structures by yourself for finding the tradeoff between explicitness and comfort that suits better your style and your project. Thanks to and . @mxstbr joshwcomeau Credits and useful links by Mark Erikson React/Redux Links list in a pull request of the awesome Ignite boilerplate repository (which uses a structure just like the one I showed you) This discussion , because he inspired me to write this post and because we had an interesting discussion about Redux that you can find Steve Kellock here , because all the stuff he posts is super-interesting and well written (and thanks for Redux too! :P) Dan Abramov , a bit outdated but I’ll try update it as soon as possible This baby boilerplate is how hackers start their afternoons. We’re a part of the family. We are now and happy to opportunities. Hacker Noon @AMI accepting submissions discuss advertising &sponsorship To learn more, , , or simply, read our about page like/message us on Facebook tweet/DM @HackerNoon. If you enjoyed this story, we recommend reading our and . Until next time, don’t take the realities of the world for granted! latest tech stories trending tech stories https://upscri.be/hackernoon/
Share Your Thoughts