Strategies for dealing with routing, data fetching, authentication and workflow testing without the UI layer Managing an open source project like gives me the opportunity to peek in many code bases. While doing so, I discovered that most React applications are not driven by the state that is stored in stores; they are also driven by the logic in mounting components. That might sound a bit vague so let’s look at two examples: MobX Interpreting route changes is often done in components; especially when using react-router. The router constructs a component tree based on the current URL, which fires of handlers that will interpret parameters and update the state accordingly. componentWillMount Data fetching is often triggered by the fact that a component is about to be rendered, and kicked off by the lifecycle hook. componentWillMount Although this approach is very workable, it has some downsides: First, we cannot reason about our application state and flow by looking at the state of our stores alone. Secondly, we need to know which components will be rendered for certain routes and what activities they perform when being mounted. To put that in a picture: That doesn’t look like our UI is a function of our state. It looks more like our state is initially function of our components. In this blog, I will show how we can inverse this relationship to the following image: This approach has better decoupling of state and UI. This has a few advantages: The complete application flow can be tested without ever needing to instantiate a component. More components can be dumb; they don’t have to fetch data or process routing. Our stores become more like a state machine, making it easy to follow the transitions of our application. A sample application To demonstrate this, we will build a simple document viewer application. The application itself is trivial, but features routing, authentication and data fetching to give it some similarity with real applications. The application consists of two pages with the following properties: There is a document overview (route “ ) document/” There is a document view page (route “ ) document/<document-id>” The overview can be accessed by anyone, but to be able to see the the contents of a specific document, one has to be logged in. This animation shows the entire feature set of the application. Note the changes in the URL bar, loading and error messages. The application is built using React and . Nonetheless, I think the principles shown can be applied to, or already be found in many existing frameworks. The application is initially generated using the . The full source code can be found in this . MobX mobx-react yeoman generator github repo ViewStore: captures the application state The majority of the application state will be captured in the . It captures two important pieces of data: the and the . reflects the identity of the logged in user. describes which page is currently visible and captures the data needed for that page specifically. ViewStore currentUser currentView currentUser currentView Now we can introduce our first action, which updates the to reflect that we are showing the overview of documents. currentView First of all, our store accepts as a constructor parameter. This is basically a tiny wrapper around the API. It automatically parses JSON and bails out on any non-200 response. We pass it explicitly to our store so that we can easily stub it in our unit tests. fetch window.fetch The method is the interesting part, it updates the and starts fetching documents from our HTTP server (a dumb static file server in this demo). The promise returned by is passed to from the package which turns the promise into a observable. This allows MobX to observe the progress of the promise, both in the UI and during our tests. showOverview currentView fetch fromPromise mobx-utils MobX Testing the documents overview At this point, the ViewStore captures the current state or “route” of our application and fetches the necessary data. Since there is no browser specific stuff in the ViewStore we can easily test this server-side using node and . In our tests, we stub with a simple file system call (I am well aware that there are awesome libraries that will do this better, but I want to keep this simple and transparent for the sake of this blog). tape fetch Note that we use MobX’s to wait until the observable promise has settled and then check whether the correct number of documents have been loaded. when Showing a specific document Time for a more challenging action; opening a specific document. Remember, only logged in users are allowed to do this. To check whether a user has logged in, we introduce a computed property that checks if the is set. The function is very similar to , except that this time we immediately reject the promise if the user is not logged in, instead of fetching the data. currentUser showDocument showOverview Note that we still update the . The advantage of this is that we can already update the URL (see below) and reflect in the UI the intended location of the user. currentView Storing the observable promise inside the has a nice benefit:it eliminates race conditions. The observable promise that is visible to the outside world as is always the one created by the last call. currentView currentView.document showDocument Authentication To support a scenario where the user actually logs in, the ViewStore exposes the action which takes a and The callback will be invoked with on a successful login, and otherwise. performLogin username, password callback. true false (Yes, I do realize this is the most horrible way to perform a login ;-). A decent login call would add some noise to the example but not fundamentally change the approach itself) With authentication in place, we can introduce two additional tests to check whether we can(not) access documents with(out) logging in. Similar to the previous test, we invoke the actions that transition our store to the correct state and we check whether all promises will settle correctly. Pretty neat so far, right? We have tested the entire workflow, and there is no single component or routing library involved yet. The ViewStore is completely agnostic of those things. To the DOM! Time to render our ViewStore! Since all our logic is captured in the ViewStore, most components can be dumb, stateless components. First there is the ‘App’ component that takes the store and initializes the proper view based on the name of the store’s using a simple switch statement. In addition, it shows the currently logged in user. currentView.name The component for rendering the document overview is pretty similar to the one that renders a single document. The latter is more interesting so we omit the for now. (For simplicity all components are put in the same file in this example). sources of the DocumentOverview The component switches on the state of the observable promise which was created by the action.Based on the state of the promise it renders either a loading message, error message or the resolved document. Document view.document showDocument But before rendering the document, we check if the current user is authenticated. If that isn’t the case, we render the login form and provide an callback. If we didn’t check this, the user would see the authentication error of the rejected promise. Which is not very user friendly. afterLogin document To be able to see the “loading” message you can enable network throttling in the chrome devtools: The login form The login form itself is pretty straight forward as well. It has some local observable state storing the current username, password and feedback message. The is invoked once the user has logged in successfully, which is done through the action. afterLogin ViewStore.performLogin Routing: translate route to state We have not implemented routing yet. As stated earlier, we want the router to invoke actions directly on our stores. Rather than indirectly by constructing a component tree and firing hooks. This makes testing and reasoning simpler, as the UI is purely based on the state that lives in our store. componentWillMount Calling store methods when a route changes is trivial. Any generic routing library can do this. You can use for example the library (which also powers react-router) but in this example I’ve picked which has a convenient URL matching mechanism. JavaScript history director With it is pretty simple to setup the routing. We define two routes that trigger either the or actions. The latter is used as the default route as well. Note that the function lives outside our store as it is largely a browser-only thing. director showDocument showOverview startRouter Routing: translate state to route That was pretty straightforward. Whenever a URL is entered in the browser’s address bar the store will transition to the correct state and the correct UI is rendered. However, the inverse process is missing. If we click a document in the overview, the URL in the address bar should be updated. One could simply fix this by calling in the appropriate actions of the store. That would work, but hold your horses. That approach has two downsides. First, it would make our store browser aware. Secondly, it is a tedious, imperative approach to the problem. If your UI has many possible views, you would end up with calls in a lot of places. history.pushState pushState Consider this: the URL of the application is just a representation of the state of our application. Like the UI, it can be derived completely from the current application state. Deriving things from state, that is where MobX excels. So let’s introduce a computed property in the viewStore that derives the path representing the current view: The is an abstract string representation of our current state. But it is still just a value. We need a side effect to push it onto the history. So we set up an autorun (which can be used to automatically trigger side effects) in the earlier defined to take care of that. currentPath startRouter To avoid the URL update triggering an URL change and an endless loop, we added a simple guard to check whether the URL has actually changed before pushing a history item. Our routing flow now looks like this: Connecting the dots We now have a universal ViewStore that captures the state of our application, React components that render the state, and a function that sets up routing. We can now simply glue those parts together to have a working application instead of just unit tests: That’s all. Conclusion So here we are, we’ve just build an application that does routing, data fetching and authentication. Although it is only ~200 LOC, it’s a good representation of how to implement those concepts in real applications. We now have a high degree of decoupling between managing the application state (which is completely testable on it’s own), rendering the UI and routing. This is achieved by (1) removing the responsibility of data fetching and route interpretation from the c_omponentWillMount_ handlers of our React components and (2) making sure that the global state of the UI is managed by the ViewStore. This makes both testing the components and testing the application flow simpler. I hope this gave you a nice overview on how one setup the project structure in a MobX application (or other frameworks) by clearly delineating the state and the things that can be derived from it. If you want to learn more about MobX make sure to check the ! could free egghead.io course To summarize, the sources: ( ) ViewStore.js tests App.jsx router.js fetch.js Supplementary notes answering potential questions :) You might have noticed that I didn’t put of state in the viewStore. Only the state that is relevant for different parts of the application, or that should be persisted while navigating etc. all I don’t have the opinion that one must remove all hooks. I just want to express that one should use them with care. Avoiding them makes the application flow more clear and components simpler. componentWillMount Don’t get me wrong. React-router is an awesome tool. React-router is probably the most used router lib in combination with MobX and there is no reason to throw it all overboard if you already have these things in place. The observation in our projects was that there is often a component in the root of the component tree which sole purpose is to update the state on route changes. We noted that different routes often used this very same component as route handler. Which raised the question whether one actually needs the component tree to be constructed when just changing the state directly would have yielded the very same result. Yes, using decorators is completely optional in MobX.