- The put effect instructs the runner to dispatch or “put” an action back onto the state, which in this case is created by an action creator.
- This sort of set up does tend to yield to a lot of duplicated bootstrapping code for each Module.
- We then structure the contents inside the Module’s files with the same format for every Module of the application, enforcing uniformity.
- Container : The smart , top rendering class that injects all props from the module file including state and action creators.
- Allowing Redux to handle the state of the application in a singular/global context takes the guesswork out of knowing what inner-dependencies exist within a large application.
So you’ve decided to try out Redux as your application state manager. Or perhaps you’re investigating better options. Good for you. With Redux the good new.
@hashnode: Learning #Redux? Here are some great tips for a better Redux architecture. 🌟
So you’ve decided to try out Redux as your application state manager. Or perhaps you’re investigating better options. Good for you. With Redux the good news is your application will enjoy a productivity boost from the simplicity of knowing precisely where data logic lives. However, Redux alone cannot protect you from a fashionable, spiced-up spaghetti mess. How does it fit into a multi-tiered application composed of several orders of widgets and components that each rely on asynchronous data? In order to save yourself from this ugly monster you’ll need a higher order architectural convention.
We, at HPE, were facing the same challenges when trying to build a massive React-based UI application. Redux offered a good starting convention for organizing the flow of data through a one-way, globally accessible pipeline BUT it didn’t go quite far enough in describing how the application should be structured.
We’ll begin with basics.
React has already proven itself in both speed and its ability for headless rendering, and its wide use has ensured no end to UI engineers who can work with a codebase. Since React simplifies UI rendering by enforcing a unidirectional, top-down data-flow it makes for good pairing partners with Redux.
Redux (paired necessarily with React-Redux) is a great benefit for medium-to-large applications because it offers a convention for how data is fetched, consumed, passed from one component to another, and ultimately displayed in the UI. Allowing Redux to handle the state of the application in a singular/global context takes the guesswork out of knowing what inner-dependencies exist within a large application. With Redux handling the updates to the global application state React can focus wholly on presentation and the handling of user events through props alone.
can certainly be used within components but should be limited as much as possible to simple, interim state that isn’t critical nor likely to be used by any other piece of the application. In these cases the usage should be heavily documented as well.
The reason why Redux is used instead of local, ad-hoc changes within the presentation layer or some other centralized store where accessors can mutate state (like MobX, e.g.) is because these systems can inevitably leave data flow inconsistent across the application design translating to longer ramp-up time and more difficulty debugging. In fact, to enforce this and protect references to the state we have wrapped everything in Immutable.
prop in React components is highly discouraged. Instead, action creators are used exclusively to post updates to the state so that components can remain totally agnostic to state schema design:
The immediate benefit of constructing an application this way is the certainty that every component and its data flow is architected the exact same way. An engineer who is familiar with this pattern can then jump into another area of the application she doesn’t yet have experience with to make changes with little learning curve. Furthermore, she can easily scaffold a new component module without the guesswork of what pieces to create or the How?.
. Without going into the virtues of testing suffice it to say it becomes important to make testing as easy and fluid as possible for team members, and this is easiest when presentation and state management concerns are kept as separated as possible.
Continuing in this same philosophy, we have embraced an architecture very similar to Redux Ducks and Reactizer. The idea is to keep the application as decentralized as possible by allowing each Module to be responsible for its own feature requirements while at the same time keeping its data in the global store.
New feature modules can be added at any time to extend the application and older features can be upgraded without extensive changes across the app.
Each Module contains the following units:
An example file structure might look like:
file might look like this:
As you might guess, when every module conforms to this pattern it gives us the benefit of knowing exactly where all of the elements of our large and complex application live without sacrificing the agility of using open-sourced libraries instead of a monolithic framework.
If you have experience with Redux you might by now be wondering how asynchronous actions are handled, like adding a new todo that involves POSTing to an API server and receiving a response. Redux has no opinion about how these action sequences are preformed but of all the available addons out there we have found great value in Redux-Saga.
Any modern application is going to have asynchronous actions. As such, we don’t want these actions to block our application as we wait for them to resolve. In fact, we want these actions to spin off “side effects” that can run in sub-processes that can then later report outcomes back to our store. This is where Redux-Saga comes into play.
Saga effects are really just object descriptors defined by Redux-Saga that are generated by factories and are interpreted by the coroutine runner to produce effects. To show a simple example:
which would then run concurrently alongside any other forked sagas.
effect instructs the runner to dispatch or “put” an action back onto the state, which in this case is created by an action creator.
Again, this example is very simplistic and doesn’t handle error handling (such as if fetch rejected) but it illustrates the potential power that coroutine runners can afford engineers by simplifying otherwise complex asynchronous flows into sync-flowing processes. As an added bonus, since Redux-Saga uses declarative effects unit testing becomes that much easier since apis no longer need to be mocked. To see more examples be sure to check out the Redux-Saga docs.
file might look something like:
Each Module, on load, forks the main module saga and combines the reducer to Redux’s store. The rest of the Module takes care of itself.
Of course, the amazing benefit of using Redux’s middlewares still applies here so any shared store logic that should extend to more than a single Module should naturally go into the App’s middlewares. (In particular, look for time-traveling, state persistence, authentication services, route pushing, and more.)
While this setup has proven easy to grasp and extend upon within our large application it does have some areas for improvement.
HoC. While this makes organization simple it also means every change or addition of a prop equates to changes to every JSX component in the tree. This gets tiring.
There’s also little agreement about what constitutes “common” or “global” components that can be shared across the application. Where should these go.
is imported for this purpose one might find they have inadvertently imported most of that entire Module.
Lastly, this sort of set up does tend to yield to a lot of duplicated bootstrapping code for each Module. While I’m sure this could be alleviated with a little ingenuity and forethought doing so might also suffer from the same kind of abstractions that we’ve been striving to avoid.
I hope you have been inspired to use Redux in a slightly different and specific way knowing that the benefits of organization really does evaluate to gained momentum. Have fun, push the boundaries, and as always, learn something new.