Meiosis Documentation

Table of Contents

SAM Pattern

The SAM (State-Action-Model) Pattern, by Jean-Jacques Dubray, is a way to structure code into these parts:

You can find more details and explanations on the SAM web site.

While not identical, Meiosis is similar to SAM:

The main difference is that in SAM, actions present proposals to the Model, which can accept or reject them. Thus you need to be able to inspect proposals. In Meiosis, patches are merged into the state with scan and the accumulator function. So instead of inspecting proposals, the acceptors inspect the state and return patches to make any necessary changes and produce the accepted state.

Acceptors also play the same role as SAM's State, since they can transform the state for the view to consume.

Services are triggered when the state changes and can issue updates; they correspond to SAM's Next-Action-Predicate.

Meiosis Pattern Recap

Remember the fundamental Meiosis Pattern:

const update = flyd.stream();
const states = flyd.scan(O, app.initialState(), update);

Actions send patches in the form of objects or functions to the update stream. Using scan and the accumulator function, we produce a stream of states. We can then use the view library of our choice, passing the current state and the actions to the view.

In Services and Accepted State, we added accept and services:

const accept = state =>
  app.acceptors.reduce(
    (updatedState, acceptor) =>
      O(updatedState, acceptor(updatedState)),
    state
  );

const states = flyd.scan(O, app.initialState(), update);

states.map(state =>
  app.services.forEach(service =>
    service({ state, update, actions })
  )
);

A Navigation Example

Say we have navigation between different pages. Clicking on a section of the navigation bar shows the corresponding page. To navigate, we have actions that update the state to indicate the current page. The view uses the state to render the corresponding page.

The example is below. Notice how you can go to different pages; From the Settings page, clicking on Logout sends you back to Home; and the Data page has no data to show, so it just displays a Loading, please wait... message.

Accepted State

SAM has the concept of an acceptor function in the model which receives the values presented by actions and updates the model accordingly. In Meiosis, acceptors are combined into a top-level accept function that transforms the state into the accepted state, preparing it for the view.

In SAM, the Model needs to be able to inspect proposals. In Meiosis, patches are applied to the state and then passed to acceptors. Thus we don't require to have "inspectable" patches -- this wouldn't work with function patches, for example -- because acceptors run on the state.

In the code below, an action updates the state to navigate to the Settings page. But if the user is not logged in -- the user is not in the state -- the acceptor changes the state to navigate to the Login page instead:

const settingsCheckLogin = state => {
  if (
    state.pageId === "SettingsPage" &&
    state.user == null
  ) {
    return {
      pageId: "LoginPage",
      returnTo: "SettingsPage"
    };
  }
};

// ...

const app = {
  // ...
  acceptors: [settingsCheckLogin]
};

Below, try it out:

More Accepted State

The State function in SAM looks at the model and makes any changes necessary to produce application state that is suitable for the view.

Acceptors in Meiosis perform this function as well. For example, we want to prepare a login form by initializing the username and password to blank strings. Further, we want to clear out those values when navigating away from the login page:

const prepareLogin = state => {
  if (state.pageId === "LoginPage" && !state.login) {
    return { login: { username: "", password: "" } };
  } else if (state.pageId !== "LoginPage" && state.login) {
    return { login: null };
  }
};

As a convenience, if the user clicks on Settings without logging in, we want to return to the Settings page after they have logged in, since that is where they were trying to go. We can use a returnTo property to indicate this, and make sure we clear it out after using it:

const checkReturnTo = state => {
  if (state.user && state.returnTo) {
    return { pageId: state.returnTo, returnTo: null };
  } else if (
    state.pageId !== "LoginPage" &&
    state.returnTo
  ) {
    return { returnTo: null };
  }
};

We only need to add these two functions to our acceptors array:

const app = {
  // ...
  acceptors: [
    settingsCheckLogin,
    prepareLogin,
    checkReturnTo
  ]
};

Try it out below. Now you are sent to the Settings page after logging in, if you had previously attempted to go to Settings. Also notice that if you go back to the Login page, the form is now cleared out.

Next-Action-Predicate

The final part of the SAM pattern is the Next-Action-Predicate (nap). This is a function that looks at the application state and decides whether to automatically trigger another action (the next action).

Meiosis services have the same functionality. Let's use this to load the data on the Data page. We want the action to complete -- navigating to the Data page and showing the please wait message -- but then if there is no data in the application state, we want to automatically trigger the action that loads the data:

const dataService = ({ state, actions }) => {
  if (state.pageId === "DataPage" && !state.data) {
    actions.loadData();
  }
};

const app = {
  // ...
  services: [dataService]
};

Now if you go to the Data page, you will see the please wait message for a couple of seconds, and then a message saying The data has been loaded. If you navigate away and then come back to the Data page, the data is still there. But if you click on Logout, the data is cleared out of the application state.

Try it out:

Table of Contents


Meiosis is developed by @foxdonut00 / foxdonut and is released under the MIT license.