Introduction
React is really flexible, it seems that we are not forced to follow a specific architecture when programming interfaces, unlike with other libraries, it is something like coding a plain view. With small web apps, this is cool, but... as soon as your app starts to grow, the speed with which you code will decrease progressively, contrary to if you had defined your architecture from the beginning of the principles.
My Architecture
In my case, I am not using Redux for state management... instead, I am using React Context + React Hooks.
This is my current project structure (serverless app built using firebase):
/app
/components
/Activity
/Authentication
/Profile
/Buttons
/Text
/Inputs
/Giphy
/Messaging
/HOCs
...
/screens
/Activity
/Authentication
/Profile
/Messaging
...
/contexts
/Users
/Content
/Auth
...
/hooks
/auth
/profile
/users
/content
/badges
/i18n
...
/navigation
/Stacks
/Tabs
...
/services
/third-party
/firebase
/api
...
/lib
/theme
/styles
/utils
/functions (backend)
As you can notice, I am using some kind of domain-driven design to structure my project files.
Also, I am separating concerns from screens and components using hooks, and managing complex state (or which need to be synchronized between routes) inside contexts that contains the respective reducers.
This seems to me like some kind of MVC. Where the View is composed by all my React Functional Components, the controller is composed by all my Business and UI hooks, and the data of my Model is contained inside Contexts (or, at least the dynamic data, because of efficient reasons).
As you can see, I have a folder "services" which is just the interface that my business hooks use in order to connect to my server (cloud functions).
Questions
Does this architecture have a name (flux/redux??)? I mean, with the passage of time as a React programmer, mistake after mistake, I have ended up organizing my projects like this, in a "natural" way.
Is it an anti-pattern to split all my components logic with hooks? I mean, all the functional components of my project just contain the event handlers or the JSX to render the UI. I have moved every single block of code to hooks, some of them contain the logic of my business, others simply complex logic related to the graphical interface (animations, ...)
Which advices do you give to me in order to refine my current architecture?
useSelector with React Context? I have implemented some custom hooks that just read and compute derived data from contexts... as I can't use "useSelector", I don't know if this is something typical, because they just consume the necessary contexts (useContext) and then execute some calculations.
Is it Redux really necessary? For a medium-large project, I have handled it well using React Context and with the help of the hooks my code has been quite clean. Do you think that over time, as the project continues to grow, it will be necessary to move to Redux?
Are react hooks the controllers of an application?
Well, not completely sure that this is the right place to put questions like this, but let try to answer, from my point of view, to these points.
Answers
I don't think this specific architecture has a name (like, for example, this one, that has a name https://www.freecodecamp.org/news/scaling-your-redux-app-with-ducks-6115955638be/). In any case the name would not be "Flux" or "Redux" since these names are more related to how data is treated instead of how folders are structured in the project. I don't think there is some strict rule about folder hierarchy to follow to be fully compliant with Flux or Redux patterns. For sure there are best practices and conventions, but they are not mandatory.
To answer this point, let me share this link https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0 about an article posted by Dan Abramov. I am sharing this article because of the last update made (the article is dated 2015, but there is an important update made in 2019). As you can see seems that you are doing it good since you are putting the core logic in hooks. Just a note about this point: you said "functional components" but I think you were referring to "presentation components", this is an important distinction because "functional component" means that your component is based on a function (instead of a class), "presentation component" instead means that the component does not contain business logic. A "presentation component" can be both class-based or functional and a functional component can contain business logic (class-based component are being replaced by functional ones, but this is another story).
Some advice: be coherent with capitalization and casing (you are mixing uppercase and lowercase, dash-case and camelCase, usually I like to name every file or folder in dash-case, but it depends on you); nut sure if HOCs folder should be here; maybe you can put all the utils (lib, theme, styles and utils itself) in a directory called utils where each util is named property;
About context, and this is a controversial topic, just want to share some considerations taken from docs https://reactjs.org/docs/context.html#before-you-use-context and share my opinion on that. The idea behind context is "Context provides a way to pass data through the component tree without having to pass props down manually at every level", as per documentation subtitle. So, basically, it si something created to avoid "property drilling", as exposed here https://medium.com/swlh/avoid-prop-drilling-with-react-context-a00392ee3d8 for example. This is just a personal point of view but, maybe, is better to introduce Redux for global state management instead of using Context API.
Don't be scared to use Redux. Be scared if, while using Redux, you have tons of duplicated lines of code. In this case you should think about how to abstract your actions and reducers (for example with action creators). If you will be able to generalize stuff like "getting a list of items from your backend", you will realize that your code will not just have less lines of code than a repetitive one, but it is even more readable and coherent. For lists, for example, you may have an action like const getListOfNews = list("NEWS_LIST", "/api/news/"); where list is an action creator like const list = (resource, url) => (params = {}) => dispatch => { // your implementation... };, something similar with reducers.
No, they just "let you use state and other React features without writing a class" as said here https://reactjs.org/docs/hooks-intro.html from docs. It is important to avoid trying to adapt a pattern like MVC to something that was created with different ideas, and this is a general advice. Is like if you are coming from Angular and you try to work in the same way in React. Basically you should work with React, or other libraries/frameworks, without trying to transform them from what they are to what you already know.
Related
I'm coding a small library with a simple structure: one parent component can contain multiple components of the same type as direct children. Here is a sample schematic diagram for the app:
By current design, a ChildComponent must address a variety of properties of their parent MainComponent, and I am looking for a good practice that can help achieve that or an alternative of an app structure that will lead to a good practice.
My considerations:
Using Context API. This won't go well with the goal in mind because of the nature of contexts. As per linked documentation:
Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language.
Passing all required props from MainComponent to ChildComponent. Despite I meet this approach quite often, I don't think it is good because it leads to duplicate code.
The simplest way is with Props, as you already mentioned. If you are passing only one level down it is probably the best option:
https://reactjs.org/docs/components-and-props.html
If you need to pass code to several children and grandchildren, using props will get annoying, that is called "propdrilling". You can avoid it using a Context that will provide to all the components that want to consume it:
https://reactjs.org/docs/context.html
Another alternative for larger applications is Redux. Is has a store and enables your components to interact with the Redux store. The downside is that it requires a lot of boilerplate.
https://react-redux.js.org/
I just started learning Vue and Vuex. I saw that I have the same code in different components. I started using Vuex and i store my data inside index.js and it's really helpful.
My questions is, how can I store click functions with vuex. In the code below is simple example
<button #click="clickMe">Click Me</button>
methods: {
clickMe() {
console.log('Hellouu')
},
}
Keeping the logic of a component in itself is what component-based approach is about. A click listener on a component or its sub-element is very personal and private to a component.
Mixins
But.. let's say you have a situation where you have the same exact logic that needs to be executed on-click on multiple components, the answer will be to use mixins. Store is not the place to do it. This holds and is true for any case where you need to 'share reusable functionalities'.
Apart from providing flexibility, mixins have a number of benefits, for instance, you can take advantage of the option merging rules within mixins to override the onClick() method described in a mixin to cater for a special case within one of the components, but continue to use the rest of the common logic the mixin contains.
Note: Be careful though, Hook functions with the same name are instead merged into an array so that all of them will be called. Mixin hooks will be called before the component’s own hooks.
Read more (optional)
As a great real world example check out this repository: https://github.com/buefy/buefy. Its a UI components library that is built with Vuejs. You can find many more examples and best practices in such open source repositories and these are always a great reference point for writing better code.
So I've played a lot with Vue, and now that my app has become large, I am having doubts about how to organize it.
I understand components and that they make sense when you need to re-use them many time on the same page, for example, a "custom select box" component, that will likely be needed in many places.
But what about components that will only have once instance? Example: a administration dashboard interface that has 3 areas: a sidebar with some navigation, a main area with stuff you can edit, based on what is selected in the navigation, another sidebar with stuff related to the main area. Do all these need to be separate components? Because I don't see any benefit of doing that if there is only one instance of each on the page. On the other side, if I stuff all the code in a single "app" component, I could simplify some of the code (less variables)
Summary - typical reasons for using components:
Maintainability.
Rendering performance via component boundaries.
Loading performance via chunking.
If you find that using fewer components improves maintainability then that's fine. It may well be the correct design for your application.
Verbose version below.
The primary reason for using components is to improve maintainability.
Components that are reused in many places such as a select-box are obviously easier to maintain than repeating the same code over and over. However, it is worth considering why that is. It's not just that duplication makes it more difficult to make a change to all of the select-boxes. The other key benefit of using a component is that the extra level of abstraction can reduce mental overhead.
Let's say someone trying to maintain the code sees something like this (pseudo-code):
<select-box :some-prop="blah">
<select-box-option v-for="something" />
</select-box>
Immediately it's clear that this is a select-box. All the gory implementation details of the select-box are hidden away and we don't need to worry about them. By looking at the props, child components and events we can quickly deduce what data goes back-and-forth between the parent component and the select-box. This is just separation of concerns.
This benefit also applies to components that aren't reused. Let's take the sidebar example. We might see this in the template for our main component:
<nav-sidebar #navigate="onNavigate" />
The component's name allows us to quickly identify it as the sidebar. If our current task doesn't involve the sidebar then we can just skip over that bit of the template. As the code has been moved off to a different file we have no difficulty establishing which bits of the code are part of the sidebar and which bits aren't.
In this example the nav-sidebar doesn't have any props and only has a single event. From that we can start to draw some conclusions about how these components interact. It would seem that the nav-sidebar doesn't need anything passed from the main component, it could quite happily live stand-alone. If we need to debug a problem with data flowing the other way we'd almost certainly start with onNavigate.
We couldn't start making deductions like these anything like as quickly if everything was mangled together into one, big component.
Of course it could be that our deductions are wrong. It could be that the nav-sidebar does some horrible things involving $parent to grab data from its parent component. However, that just illustrates why using such techniques is considered bad practice. Maintainable code should allow developers to jump to reasonable conclusions based on the abstractions that appear to be in place.
But it is possible to go too far the other way.
A good abstraction allows you to free up some mental capacity by hiding details behind a label. A poor abstraction adds mental overhead by hiding the code you want to see behind some indirection. Add to that the difficulty of naming things and the burden of extra glue code and you may well be better off just ditching the extra layers and keeping everything inline.
The other thing that can go wrong is splitting components up in the wrong way. Separating concerns requires clean partitions of those concerns. Chop things up slightly differently and you end up with a single concern being spread across multiple components and the resulting mess is typically worse than if you hadn't bothered splitting things up at all.
Vue allows you to split up your JavaScript code in a number of ways, components being just one. Separate .js files, plugins, filters, Vuex, mixins, etc.. There are several options available to you.
Templates, on the other hand, can only really be split up by using components. If you want to break a huge template down into more manageable chunks then components are really the only way to go.
This brings us to another key reason for using components.
A template is compiled down into a render function. When that render function is run it registers reactive dependencies, just like a computed property. If any of those dependencies changes it will trigger a re-render. That runs the whole render function again. Even if that doesn't result in any changes to the DOM it will require the generation of all the relevant VNodes and the diffing algorithm will need to check all of them.
Component boundaries are also rendering boundaries. Each component makes its own decision about whether or not to render based on whether its dependencies have changed.
So, taking the nav-sidebar example, let's say something changes in the nav-sidebar so that it needs a rendering update. If the nav-sidebar is a separate component then it just needs to run the template/render function for that component. If instead we bundle all the nav-sidebar code into the main template then we'll have to re-render everything.
Vue also has support for lazily loaded components as a way to reduce the initial load time of the page. The idea is that many applications have large sections, such as admin interfaces, that aren't relevant to most users. Rather than incurring the overhead of downloading all of those components you can split the components into chunks and download the chunks when they're needed. This is usually implemented via the Vue router configuration.
Chunking aside, the typical way to use the router is to have separate components for the different pages. While in theory it is possible to use the same component for all routes that is unlikely to lead to something more maintainable. I would add that the definition of 'page' is a little fuzzy here but in most applications it's clear what constitutes a different page, resulting in a different component.
No tome on creating code monoliths would be complete without some mention of testing. Unit testing should be thought of as a form of reuse and a particularly extreme form at that. Tests have an unrelenting knack for exposing the mess of spaghetti that hides behind what you thought was a nice, clean design. I'm not going to pontificate on testing but suffice it to say that you won't be able to write unit tests unless you split things into suitable units.
Another key feature of a component is that it has its own set of properties. Its own data and its own computed properties. This sounds obvious but it gains significance when you consider looping via v-for.
<div v-for="item in items">...</div>
The example above uses inline elements instead of components. Any state can only live on the parent. That state needs to be held for each loop item, so we may end up with multiple arrays holding different aspects of the state. Computed properties are similarly difficult to implement when working with loops. We typically end up using methods instead:
<div v-for="item in items" :class="getClassesFor(item)">...</div>
Now consider the component version:
<my-component v-for="item in items" :item="item" />
Each my-component can hold its own state. Let's say, for example, that my-component has expand/collapse functionality. That can now be stored in a local data property within each instance. Likewise each instance will have its own computed properties.
Arguably this is just reuse as the v-for creates multiple instances. However, given we're specifically introducing a new component just to provide a form of property scope, I think it warrants a special mention.
I personally like to have 3 types of components.
Reusable system component. These are used for generic layouts, custom buttons, custom select box ... They are meant to be reused multiple times in the code and be very versatile.
Page/View component. The route usually routes to a specific component. This component is some kind of assembly of multiple components. The distinction allows to quickly identify the "page" of the application.
Logical division. These are the hardest to find. I tend to isolate things that are not related to each other. For instance, a footer may not be reuse but a modification in the footer should concern only the footer. Other examples are the navbar, the menu, each section of an admin. These components should be reusable as much as possible but sometimes they will be specific.
Another example: A comment system. A "comment" would be a component of type 3. A "comment thread" display would be another component of type 3 that use the "comment" component. A page would exist for the subject of a comments thread and would be of type 2.
Note that each component of type 3 and 2 may use other components of other types.
If I want to change the display arrangement of the comment thread I only have to change the "comment thread" component.
Vue components uses not only for re-use. You may split components into logical blocks. SPA for example. You can create grid based on vue components.
Here is the reason why you need to split codes even it is used only once.
With hundreds or thousands of lines of code, it's no question that
splitting code in different files can help make it more manageable.
Although, this can quickly turn into a complicated mess if the
splitting isn't thought out beforehand.
The more you split codes, the clearer it becomes.
It does not only work for Vue JS but also works for almost programming languages.
Mithril's website states:
You can only have one m.route call per application.
So I was almost able to do a work around with code splitting.
But my application is only aware of the first-level components for a given URL which it utilizes the async code splitting to accomplish.
Now the problem: Those first-level components would then like to register their own namespaced routes to leverage URL state change for their own inner components since I can't register their routes ahead of time (and Mithril prevents setting the routes again after the initial route was set by the app/wrapping component).
To further the complexity of the issue, each first level component is loaded on an as-needed basis, so I can't wait for all the first level components to load and then instantiate the m.route; routes have to be added dynamically.
I love this framework, but my use case seems like an edge case that I can't seem to resolve.
The simple solution would be to re-instantiate the m.route object after each first-level component loads, but that's not supported.
UPDATE
The purpose of my post was to find a native way to do dynamic routing and not lose functionality (such as variadic routing), but its been reinforced that that's not possible.
I replaced the entire router with an in-house one so I could support dynamic (and unknown) routes, more flexible variadic routing, better params method, and even provide get/set global data across views without a global window variable or use of the History API in that case. I still provide the rest of the functionality that Mithril does, just a little more simply.
Why not do a pull request? From what I've read on different Github pages, two big pieces of this would not work with the core logic of Mithril; and/or, is too much of an edge-case that they don't want to support it.
I'll still choose Mithril over any other framework though.
In the meantime, I built what I need, and hope Mithril 2 will have dynamic routing baked in.
Mithril's router is intended as a relatively simple solution to easily enable standing up simple SPAs, dynamically registering routes isn't part of the current design.
I think you'll probably be best served by finding a router that supports the dynamic route registration you require and using that.
Integration of that router with Mithril could be naive (using m.mount() when routes change) or more complex by emulating a bit of the logic of the existing router API.
Mithril's router is not the most advanced tool. Although you can work around it pretty much however you want.
There is a way to make new routes dynamic.
I made a little jsfiddle a while ago. https://jsfiddle.net/Godje/cpzLtoyz/
You are interested in lines 2-11 and 63-92.
Although they aren't dynamic in the fiddle, you can make a function, to replace that switch on line 73, which will process your routes and return a component needed to be rendered. That way if you have an array stream with all the URLs or other routes you want, you can have a function process each param on each route-change call and check it with the array.
Sorry for a messy response. Writing an exact solution to the problem requires a local server.
I am a bit confused by the statements: "Renders the whole application" and "Passing state to child components".
Example 1:
I have a todos app with a AppComponent and TodosListComponent. The AppComponent grabs the array of todos from the store and passes it as a property to the TodosListComponent.
Example 2:
I have a huge application with lots state. I have like 50 components building up my app. Do I want to pass all the state from the stores from AppComponent down through all the 50 components?
So I am wondering, what is the convention? It makes more sense to me to let individual components listen directly to the stores they care about. The advantage is that only individual components rerender, but why then the concept of "the whole application rerender on state change"?
What are the pros and cons of each? What is the common convention?
There are a few ways you can handle this. I think they're all valid and have their own trade-offs.
Get all the state and pass pieces of it to children
This is the technique you specifically asked about. Using this method, you'll have some function or method available to your top-level component that turns all the data from the stores into a "big bag of state" and then you'll selectively pass pieces of this data to child components. If those components have their own children, they'll pass it along as necessary.
The upside to this method is that it makes things generally easy to debug. If you have to change the way a piece of state is retrieved from a store, you only have to change it in the top-level component—as long as it gets passed down with the same name, the other components will "just work." If some piece of data is wrong, you should only need to look in one place to figure out why.
The downside to this technique what I call "props explosion"—you can end up passing a lot of properties around. I use this method in a medium-sized flux application, and a snippet of the top-level application component looks like this:
<section id="col-left">
<Filters loading={this.state.loading}
events={this.state.events}
playbackRate={this.state.videoPlayback.playbackRate}
autoPlayAudio={this.state.audioPlayback.autoPlay}
role={this.state.role} />
</section>
<section id="col-center" className={leftPaneActive ? "" : "inactive"}>
<SessionVideo videoUuid={this.state.session.recording_uuid}
lowQualityVideo={this.state.session.low_quality_video_exists}
playbackRate={this.state.videoPlayback.playbackRate} />
<section id="transcript">
<Transcript loading={this.state.loading}
events={this.state.events}
currentEvents={this.state.currentEvents}
selection={this.state.selection}
users={this.state.session.enrolled_users}
confirmedHcs={this.state.ui.confirmedHcs}
currentTime={this.state.videoPlayback.position}
playing={this.state.videoPlayback.playing} />
</section>
</section>
In particular, there can be a lot of components between the top-level one and some eventual child that do nothing with the data except pass it along, more closely coupling those components to their position in the hierarchy.
Overall, I like the debuggability this technique provides, though as the application grew larger and more complex I found it was not idea to do this with only a single top-level component.
Get all the state and pass it as one object
One of the developers at Facebook mentioned this technique. Here, you'll get a big bag of state, just as above, but you'll pass the whole thing (or entire sub-sections of it) rather than individual properties. By utilizing React.PropTypes.shape in child components, you can ensure that the right properties are getting passed.
The upside is you pass way fewer properties around; the above example might look more like this:
<section id="col-left">
<Filters state={this.state} />
</section>
<section id="col-center" className={leftPaneActive ? "" : "inactive"}>
<SessionVideo session={this.state.session}
playback={this.state.videoPlayback} />
<section id="transcript">
<Transcript state={this.state} />
</section>
</section>
The downside is that it becomes a little more difficult to deal with changes in the shape of the state; rather than just changing the top-level component, you'll have to track down everywhere that piece of data is used and change the way that component access the property. Also, shouldComponentUpdate can potentially become a little trickier to implement.
Allow components to get their own state
On the other end of the spectrum, you can grant application-specific (that is, non-reusable) child components to access the stores and build up their own state based on the store change events. Components that build their own state like this are sometimes called "controller-views" or, more commonly these days, "container components."
The upside, of course, is that you don't have to deal with passing properties around at all (other than change handlers and properties for more reusable components).
The downside, though, is that your components are more highly coupled to the stores—changing the stores or the data they provide (or the interface via which they provide that data) may force you to revisit the code for a larger number of components.
Also, as mentioned in the comments, this can potentially make server rendering a bit more difficult. If you only use properties (especially at only the top level), you can transport them more easily to the client and re-initialize React with the same properties. By allowing the stores to determine their own data, you need to somehow inject that data into the stores to allow the components to get that data.
A common approach, and one that I typically use now, is to make every component in your application only rely on props for global application state, and then decide if it makes more sense to (1) connect them directly to flux by wrapping them in a container, or (2) allow the props to be passed from some parent container.
There are abstractions that you might be able to use to make some of these techniques more viable. For example, a Facebook dev had this to say in a comment on Hacker News:
Now all your data is in stores, but how do you get it into the specific component that needs it? We started with large top level components which pull all the data needed for their children, and pass it down through props. This leads to a lot of cruft and irrelevant code in the intermediate components. What we settled on, for the most part, is components declaring and fetching the data they need themselves, except for some small, more generic components. Since most of our data is fetched asynchronously and cached, we've created mixins that make it easy to declare which data your component needs, and hook the fetching and listening for updates into the lifecycle methods (componentWillMount, etc).
Jason Bonta from Facebook explained the concept of "Containers" in his React.js Conf 2015 talk.
To summarize: containers are simply components that wrap other components, and take care of any data-related concerns such as talking to stores, while the underlying component is focused solely on the view (markup/styles/etc.) and doesn't care where the data comes from.
This makes the component
highly re-usable because it can be wrapped with a different container when data needs to come from a different place,
not contain irrelevant state, therefore easier to implement and optimize shouldComponentUpdate, and
using composition rather than mixins for this aligns with what is likely the future of React with ES6, which does not have idiomatic mixins.
UPDATE March 2019: Look into React Hooks. With hooks, you can accomplish the same goal as described above, but abstract data-related concerns, such as talking to stores, in re-usable chunks of code that can be applied to multiple components. The ReactConf talk React Today and Tomorrow and 90% Cleaner React With Hooks by Dan Abramov does a great job of explaining hooks, and how they are different from both mixins and past composition approaches.