Mobx with Universal Router with functional React components
Published: 2020-12-23 23:19:40

An interesting alternative to React Router is the lightweight Javascript package [Universal Router](https://github.com/kriasoft/universal-router) (called UR here). It integrates nicely in a React app whenever you use a state management library. Below is an illustration of how to combine Universal Router with Mobx using only functional stateless components.

The best is to take an example: an app with a navbar where you can set an "admin" mode to protect routes, where you can search for an item and where you want to open a page which data fetched  asynchronously.

To do this in a standard 'React' way , you could use `useContext` (or localStorage) for the switch to  "admin" mode. For the data uploading, the component-page would hold an internal `useState` which will be mutated via `fetch` within a `useEffect`. If you need to use this data elsewhere, you would need to lift state up to a common ancestor.

With Mobx, we use a central state object -called `store` here- that distributes among the stateless React components. This also follows the Mobx principals: pass the full reference of the store and decompose it late, only when needed. Here, the `store` will contain the "mode" and the uploaded data and mutating actions. Used together with Universal Router, the process is different from a React / React Router way: the router will act as a controller, will distribute the state where needed, can contain some logic and will trigger the rendering. 

The key points are:
- both Mobx and Universal Router use a plain Javascript object.
- the Mobx `store` is distributed within the React components by adding an object with value `store` to the Universal Router object `context`,
- the path objects defined for Universal Router need to be orderly sequenced. They contain an `action` with the logic for the rendering a component,
- Mobx reactivity relies on the proxying methods `observable` on the `store` and  `observer` on components who uses the `store`. Calls to the `store` attributes may need the "autorun" form of Mobx `runInAction`, especially with async updates or when called from outside an observed React component.

### Setup

- imports: besides React, our imports are "mobx", "mobx-react-lite", "history" and  "universal-router". This adds an extra 50kb.

- linting:  we use  `React.StrictMode` and the [debug `configure`](https://mobx.js.org/configuration.html#linting-options) from Mobx.

### New objects

We will build two plain Javascript objects:

-  `store`: the global state object - proxied by the [`observable`] (https://mobx.js.org/observable-state.html#observable) method -  is a collection of attributes and methods that can be use by any React component. Any method that changes the state variables is wrapped with `action`.

- `routes`: it is an array of "path" objects that will be parsed by Universal Router. Each contains at least 2 or 3 keys, `path` and `action` or `children` ([when the routes are nested](https://github.com/kriasoft/universal-router/blob/master/docs/api.md#nested-routes)). Note that these are UR keywords. They are in the form:
```js
{ path: "/", action: ()=> {...}}
{path: "/", children: [{...},{...}]
```

### How is routing like with U.R.?

We build a `routes`array by appending in an ordered manner path objects. It is in the form:

 ```js
routes = [
{path:"/", action: () => {...<Home/>..}}, 
//-> "/"
{path:"/todo", 
  children: [
    {path:"/", action: () => {...<Todo/>..}},
    //-> "/todo"
    {path: "/:id", action: ()=> {Todo id={id}../>}} 
    //-> "/todo/1"
  ]
]
```

The `action` can be [asynchronous](https://github.com/kriasoft/universal-router/blob/master/docs/api.md#async-routes), just write:
```js 
async action (context){ await..}
```

This `routes` array is the input of the object:
 ```js
new UniversalRouter(routes)
```

### How do you pass props to the path objects?

The `routes` array is not declared in a React component so isn't integrated within a component. This is different from React Router where you would declare any routes within a React component and where the app is wrapped with the `BrowserRouter` context. We may need a library that handles the state and the connection between Mobx and UR is simple: UR provides a `context` object which is available to any of the path objects.  We can just add the `store` object to the `context` object. The context is then added to the constructor. For example, we pass two objects  `{store:store}`  and `{mode: "admin"}` to `context`:

```js
new UniversalRouter(routes, { context: {store, mode: "admin" }}
```


## The **index.js**

It contains the path listening, path resolving and the rendering methods.

Firstly we can use  `history = createBrowserHistory()` [from the package `history`](createBrowserHistory). 
```js
#history.js
import { createBrowserHistory } from "history";
export default createBrowserHistory();
```
We implement a listener on the History API. An `<a/>` link triggers a `history.push("/path-to-go")`, and the listener will pass `path-to-go` to the unique `.resolve()` function attached to the routes array.  The `routes` are parsed and return the matching path object found or not. The matched path object has an `action` (keyword for UR in this context) that renders some object. It can be a React element, and in this case, we can feed `ReactDOM.render` with it. This is implemented in **index.js**.

```js
#index.js
import { createElement as h } from React;
import { render } from "react-dom";
import UniversalRouter from "universal-router";

import { routes } from "./routes.js";
import store from "./store.js";

const root = document.getElementById("root");

const router = new UniversalRouter(routes, { context: {store, mode: "admin" }}

async function renderRoute(location) {
  try {
    const page = await router.resolve({
      pathname: location.pathname
    });

    if (page.redirect) {
      return history.push({ pathname: page.redirect, search:"" });
    }
    
    return render(<React.StrictMode>{page}</React.StrictMode>, root);
  } catch (err) {
    return render(h("p", {}, "Wrong way"), root);
  }
}

history.listen(({ location }) => renderRoute(location));
renderRoute(history.location);
```

## Example
 
{% codesandbox universal-router-mobx-demo-jg3um%}

The components are wrapped by a Navbar where we can set/unset the mode. On a page, we fetch async data from an API. We can navigate to nested User page via a direct link, or via a search. We can even display a further nested page. 

### Mobx store 
In the example, we have two attributes, `users:[]` and `modeAdmin: ""` and four methods that change state. One is async. In the case of [async actions](https://mobx.js.org/actions.html#asynchronous-actions), we need to use the **IIEF form** `action(()=> [todo])()` aka as **`runInAction`**. 

```js
#store.js
import { observable, action, runInAction } from "mobx";

const store = observable({
  users: "",
  addUsers: action((data) => (store.users = data)),
  fetchUsers: async () => {
    const query = await fetch(url);
    const response = await query.json();
    runInAction(() => (store.users = response.data));
  },
  findUser: function (id) {
    return store.users.find((user) => {
      return user.id === +id; //<- it's a string!
    });
  },
  modeAdmin: "",
  toggleMode: action(() =>
    store.modeAdmin === ""
      ? (store.modeAdmin = "admin")
      : (store.modeAdmin = "")
  ),
})
```

### An example of `routes`

The router will also be our controller. It contains some logic, call the data and renders the components.
There are several possible implementations. This example [uses the middleware construction](https://github.com/kriasoft/universal-router/blob/master/docs/api.md#middlewares) with `context.next()` to build the routes array. This way, we can easily wrap any component with the navbar. We can also use `Suspense/lazy` or directly `import` for code splitting. We can also implement logical redirections, implement functions before returning the component such as data loading.... 
```js
#routes.js
import React,{Suspense, lazy} from "react";
import LazyNavBar = lazy(()=>import("./NavBar"))
const routes = [
  {
    path: "",
    async action(context)=>{
      const content = await context.next()
      if (content.redirect) { return content}
      return(
        <Suspense fallback={<Spinner/>}
          <LazyNavbar>{content}</LazyNavbar>
        </Suspense>
      )
    },
    children:[
      {
        path: "/",
        async action() { 
          const {default: Home} = await import("./Home")
          return <Home store={store}/>
        },
      },
      {
        path: "/users",
        action: async(){ ....} 
      },
    ],
  },
  {
    path: "(.*)",
    action: () => React.createElement("h1", {}, "404: No way"),
  },
]
```


#### Redirection

We implement a [redirection](https://github.com/kriasoft/universal-router/blob/master/docs/redirects.md) / protected route via a checkbox in the navbar which sets the `modeAdmin` attribute  from the `store`. We passed an object `{mode: "admin"}`  and the `store` to UR `context`, so both values are available in the `routes` and we can compare them. Then it is interesting to see how Universal Router implemented a redirection. In short, if the previous condition is false, return an object `{redirect: "/a"}` instead of a React element, and let  `next()` return it too, and make  `.resolve()` conditionally push the value "/a" to the history when the key "redirect" is present, so this loop backs and eventually renders the React element attached to the path "/a".

Note that we can't use directly `store.modeAdmin` in the routes as the Mobx linting will complain "..being read outside a reactive context". This isn't indeed called within an observed React element thus we have to use the "autorun" `runInAction` to pass.
```js
#routes.js
let whichMode;
runInAction(() => (whichMode = store.modeAdmin));
if (whichMode !== context.mode) { return { redirect: "/" } }
```

#### Async call
We do an async call to populate the component `Users`.  We firstly wrap the `store.fetchUsers()` with `runInAction`. We can chain to display the component. We can also use [`await when`](https://mobx.js.org/reactions.html#when) with a boolean `store.users.length>0` to let you wait for changes in observable state. 

```js
#routes.js
return await runInAction(()=> store.fetchUsers())
.then(() => import("./Users"))
.then(({default: Users})=> <Users store={store}/>)

or
runInAction(()=>store.fetchUsers())
await when(()=> store.users.length>0)
const {default: Users} = await import("./Users")
return <Users store={store}/>
```

#### Search
The search will be simple. There is an input to enter the number that correspond to a user. We build a query string with this number, in the form **/users?nb=1**. Then the `.resolve()` parses the routes looking for **/users**. The matching action has a function that reads if a query string is present. If yes, extract the number and perform a redirect by returning `{redirect: "/users/1"}`. The redirect works then as above.
```js
# routes, path "/users" 
const sStg = new URLSearchParams(window.location.search);
const id = sStg.get("nb");
if (id) { return { redirect: `/users/${id}` } }
```

### Conclusion
I wrote 3 posts on my self introduction to Mobx mostly because I did not find code examples not using classes, and also as a reminder. I built [a tiny todo app](https://dev.to/ndrean/a-comparison-of-state-management-in-react-with-mobx-vs-state-lifting-54l4), did a simple [POST  request](https://dev.to/ndrean/fetch-with-mobx-in-react-1omi) from a component and did this tiny navigation app with a GET request. All this used only 4 Mobx verbs: `observable`, `observer`, `action`, `runInAction`.  Given the magnitude of this work,  the usage was  fairly simple with the Mobx linting. 
As for Universal Router,  once you understand how Universal Router works, it lets you build navigation without pain. It also integrates easily React and interacts very easily with  Mobx. These libraries also greatly lowered the number of lines of this little app compared to a pure React + React Router solution, without Redux ;)