Chapter 7: Application state, App HOC, store and MobX. Toggle theme API. Team API. Invitation API.
Last updated April 2025.
The section below is a preview of SaaS Boilerplate Book. To read the full text, you will need to purchase the book.
In Chapter 7, you will start with the codebase in the 7-begin folder of our saas repo and end up with the codebase in the 7-end folder.
We will cover the following topics in this chapter:
Application state, withStore HOC, store and MobX - Updating App HOC - store, observable, decorate - initializeStore, getStore, server-side rendering, action - Data store for User - Updating withAuth HOC - Updating YourSettings page - Testing store infrastructure
Toggle theme API - Layout - Store method toggleTheme - toggleThemeApiMethod API method - Express route /user/toggle-theme - Static method toggleTheme - Testing toggle theme API
Team API - Model and static methods - Team - Updating User model - Team - Express routes - Team - API methods - Team - Data store and store methods - Team - Updating main store - Team - Initial data from App.getInitialProps - Team - teamRequired - Team - CreateTeam page - TeamSettings page - Testing Team API
Invitation API - Updating TeamSettings page - InviteMember component - Invitation page - Updating LoginButton component - Invitation data store - Invitation - Updating Team data store - Invitation - API methods - Invitation - Express routes - Invitation - Model and static methods - Invitation email template - Updating Team model - Invitation - Testing Invitation API
In this chapter, we will learn about data stores and their purpose. We will build a data store for User and modify our APP project to work properly with MobX. We will introduce multiple new models (Team, Invitation, Discussion, and Post) and build the corresponding infrastructure for these models in both APP and API projects.
Inside the API project, we will build many "Model - Static method - Express route" infrastructures.
Inside the APP project, we will build many "API method - Store - Page - Component" infrastructures.
Application state, withStore HOC, store and MobX link
As your SaaS boilerplate grows and becomes more complicated, there is a growing need for management of your application's state. The application's state is all data associated with your application (objects, arrays, etc). For example, the user object with public parameters in APP is part of our application's state. In this chapter, we will add more data to our application's state (Team, Invitation, Discussion and Post).
Why do we need to worry about carefully managing our application's state? What happens when a project grows? Here are a few consequences:
- Number of pages (page components), higher-order components, and regular components increases
Let's say you need to display user information (displayName and avatarUrl) on multiple pages. As the number of pages grows, you, as a web developer, have to call some API method inside the getInitialProps method on each page.
- Number of user events increases (end user clicks on button, uploads file, creates or deletes post and etc).
An end user may update their avatarUrl on YourSettings page. You, as a web developer, need to make sure the user object gets an updated avatarUrl parameter inside your application's state so that all other pages of your application display this updated avatar. You can achieve this by calling an API method that sends a request from your APP to API server for every page load, but that is not efficient. You'd rather send a request to your API server only one time, get an updated avatarUrl, save it to application's state, and then use this saved avatarUrl for every page that needs it - without sending a request to the API server for every page.
- For some interactions with your web application, end users expect reactivity. For example, a user updates their
avatarUrland wants to see a new avatar right away, without reloading the page. Or a user creates a new post and wants to see this new post right away, without reloading the page. In other words, the end user does not want to reload their browser tab (server-side rendered page) or click a navigational link (client-side rendered page) to see successfully updated data on the user interface.
If we do not manage our application's state properly, we may show inconsistent and/or non-reactive data throughout our web application.
We wish there was a way to define data store for User. We want such data store to persist. When updated, we want to send a request to our API server to update that data in the database and automatically re-render corresponding components to display this data reactively to the end user. Every time an end user loads a page that requires user information, the page gets data from data store for User instead of sending a request to the API server.
The mobx package allows us to define data store with the above properties. The mobx-react package allows us to automatically re-render React components if the corresponding data changes inside the MobX data store.
Let's discuss how we will implement the above infrastructure. We can create a store object that contains all MobX data stores in our web application. We can populate the page component's props with this store object, so we can easily access it on any page with this.props.store. You already know two ways to populate props of a page component. One method is to to call the getInitialProps method. The second method is to wrap the page component with a higher-order component that can add props to the page's props. For example, let's look at YourSettings page at the end of Chapter 4. Open file book/4-end/app/pages/your-settings.tsx and file book/4-end/app/pages/_app.tsx: - getInitialProps method of YourSettings page populates user prop - App higher-order component that wraps all pages (Next.js feature) populates isMobile and firstGridItem props
In other words, at the end of Chapter 4, we used both methods to populate YourSettings page's props.
We can create a new higher-order withStore or update an existing higher-order component, for example, App HOC. Then on any page that needs user data, we can access it simply by:
this.props.store.currentUserIn this book, we chose to do the latter, updating App HOC instead of creating a new HOC.
Here is how our typical internal (non third-party) API infrastructure looks like now:

Here how it will look like with store:

You may ask why complicate things (add an extra step) and have a data store? As we discussed earlier, one benefit is to be productive as a developer. On any page, we can access this.props.store.currentUser and this.props.store.currentUser.updateProfile methods - no need to call getInitialProps and define the user prop for every page. A second benefit is that if data displayed on the UI changes in the store, the UI will get updated automatically and reactively:

Before we can access this.props.store on any page, we have to build the following parts: - Update our App HOC so it populates a page's props with store, which can be accessed as this.props.store - Define store from the above step - Update our withAuth HOC - Update YourSettings page - Test store infrastructure
Updating App HOC link
In order to automatically and reactively re-render components when the corresponding data inside the store changes, we need to do a few things:
Wrap our page component with a
Providerhigher-order component from themobx-reactpackage.Providerpassesstoreas a prop to a page component and all child components:Wrap our page component with an
observerHOC from themobx-reactpackage to subscribe the wrapped components to anobservablechange. This will automatically re-render wrapped components if there is change inobservable: https://mobx.js.org/refguide/observer-component.htmlobservableis data (object, array, parameter and etc) instorethat will change over time and trigger re-rendering of corresponding React components: https://mobx.js.org/intro/overview.htmlMobXcreates a clone ofstore(storecontains allobservables), and when data changes in your application, it gets compared to the cloned instance to conclude if data changed.
An example ofobservablein our case isstore.currentUser(object) orstore.currentUrl(parameter, string).We need to inject
storeinto our page component before it can render. We can do this by wrapping our page component with theinjectHOC from themobx-reactpackage: https://github.com/mobxjs/mobx-react#provider-and-inject
Official docs for mobx-react show the above steps can be implemented:
@inject("color")
@observer
class Button extends React.Component {
render() {
return <button style={{ background: this.props.color }}>{this.props.children}</button>
}
}
class Message extends React.Component {
render() {
return (
<div>
{this.props.text} <Button>Delete</Button>
</div>
)
}
}
class MessageList extends React.Component {
render() {
const children = this.props.messages.map(message => <Message text={message.text} />)
return (
<Provider color="red">
<div>{children}</div>
</Provider>
)
}
}In the above example, store has one parameter: color. Provider wraps child components to pass store to them. inject and observer HOCs wrap components. In our case, we decided to modify our App HOC that wraps our page component. Open book/7-begin/app/pages/_app.tsx and find this line:
<Component {...pageProps} />Based on the above example and docs, we can do:
import { Provider } from 'mobx-react';
<Provider store={store}>
<Component {...pageProps} />
</Provider>And then, somewhere before page component renders, we need to add:
inject('store')(observer(Component))Note that the official docs suggest wrapping with the observer HOC before wrapping with the inject HOC.
Alternatively, we can pass store to our page component like this:
<Component {...pageProps} store={store} />If we do so, we don't need wrap page components with inject HOC. Hovewer, we still need to wrap all other components that require reactive re-rendering with inject HOC.
We still need to subscribe our page components to observables inside store. We can achieve this by wrapping our page component with the observer HOC. Later in this section, we will do the same for the YourSettings page:
export default withAuth(observer(YourSettings));Make the above two changes to your App HOC, and you should get:
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@mui/material';
import { Provider } from 'mobx-react';
import App from 'next/app';
import React from 'react';
import { themeDark, themeLight } from '../lib/theme';
import { getUserApiMethod } from '../lib/api/public';
import { isMobile } from '../lib/isMobile';
import { getStore, initializeStore, Store } from '../lib/store';
class MyApp extends App<{ isMobile: boolean }> {
public static async getInitialProps({ Component, ctx }) {
let firstGridItem = true;
if (ctx.pathname.includes('/login')) {
firstGridItem = false;
}
const pageProps = { isMobile: isMobile({ req: ctx.req }), firstGridItem };
if (Component.getInitialProps) {
Object.assign(pageProps, await Component.getInitialProps(ctx));
}
const appProps = { pageProps };
if (getStore()) {
return appProps;
}
let userObj = null;
try {
const { user } = await getUserApiMethod(ctx.req);
userObj = user;
} catch (error) {
console.log(error);
}
return {
...appProps,
initialState: { user: userObj, currentUrl: ctx.asPath },
};
}
public componentDidMount() {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles && jssStyles.parentNode) {
jssStyles.parentNode.removeChild(jssStyles);
}
}
private store: Store;
constructor(props) {
super(props);
this.store = initializeStore(props.initialState);
}
public render() {
const { Component, pageProps } = this.props;
const store = this.store;
const isThemeDark = store.currentUser ? store.currentUser.darkTheme : true;
return (
<ThemeProvider
theme={isThemeDark ? themeDark : themeLight}
>
<CssBaseline />
<Provider store={store}>
<Component {...pageProps} store={store} />
</Provider>
</ThemeProvider>
);
}
}
export default MyApp;You've reached the end of the Chapter 8 preview. To continue reading, you will need to purchase the book.
Last updated April 2025.