Categories
Mobx

Create Mobx Observable Objects and Arrays

Mobx is a state management solution for JavaScript apps. It lets us observe values from a store and then set the values, which will be immediately reflected in the store.

In this article, we’ll look at how to create Observable objects with Mobx.

Observable Objects

Mobx provides us with the observable.object method, which can be called as follows:

observable.object(props, decorators?, options?)

If a plain JavaScript object is passed to the observable then all properties inside will be cloned and made observable.

A plain object is an object that wasn’t created from a constructor but has Object as its prototype or no prototype.

observable is applied recursively by default, so if the encountered value is an object or array, the value will also be passed through observable .

We can use the observable.object method as follows:

import { observable, autorun, observe, action } from "mobx";

const person = observable(
  {
    firstName: "Jane",
    lastName: "Smith",
    get fullName() {
      return `${this.firstName}, ${this.lastName}`;
    },
    setAge(age) {
      this.age = age;
    }
  },
  {
    setAge: action
  }
);

observe(person, ({ newValue, oldValue }) => console.log(newValue, oldValue));
person.setAge(20);

We created the Observable person object with the observable function, which takes a plain object as the first argument. Then we set the setAge method in the object as an action with the second object.

We can then observe value changes with the observe method, which takes the Observable person object as the first argument and a change listener as the second argument.

Then when we run:

person.setAge(20);

the listener that we passed into observe will run.

In MobX 4 or lower, when we pass objects into observable, only propeties that exist at the time of making the object observable will be observable. Properties that are added to the object at a later time won’t be observable unless set or extendObservable os used.

Only plain objects will be made observable. For non-plain objects, it’s considered the responsibility of the constructor to initialize the observable properties.

We can either use the @observale annotation or the extendObservable function.

Property getters will be automatically turned into derived properties.

oberservable is applied recursively to the whole object automatically on instantiation and also when new values are assigned to the observable properties. It’s won’t apply observable recursively to non-plain objects.

We can pass in { deep: false } to observable ‘s 3rd argument to disable automatic conversion of property values.

Also, we can use the autorun function to watch the values that are changed as follows:

import { observable, autorun, action } from "mobx";

const person = observable(
  {
    firstName: "Jane",
    lastName: "Smith",
    get fullName() {
      return `${this.firstName}, ${this.lastName}`;
    },
    setAge(age) {
      this.age = age;
    }
  },
  {
    setAge: action
  },
  {
    name: "person"
  }
);

autorun(() => console.log(person.age));
autorun(() => console.log(person.firstName));

person.setAge(20);
person.firstName = "Joe";

In the code above, we’ll see the callbacks we passed into autorun run when we update person.age via setAge or when we update a property directly as we did with firstName .

Observable Arrays

We can use the observable.array method to create an Observable array. It takes an array as the first argument.

For example, we can use it as follows:

import { observable, autorun } from "mobx";

const fruits = observable(["apple", "orange"]);

autorun(() => console.log(fruits.join(", ")));

fruits.push("grape");
fruits[0] = "pear";

In the code above, we passed in array to observable . Then we runautorun with a callback that runs every time fruits change.

Then when we run the array operations on fruits , we get the latest value in the callback we passed into autorun .

The following methods are also available on observable arrays:

  • intercept(interceptor) — intercept array change operations
  • observe(listener, fireImmediately? = false) — listen to array changes
  • clear() — remove all entries from an array
  • replace(newItems) — replace all entries with new ones.
  • find(predicate: (item, index, array) => boolean, thisArg?) — same as the usual Array.prototype.find method
  • findIndex(predicate: (item, index, array) => boolean, thisArg?) — same as the usual Array.prototype.findIndex method
  • remove(value) — remove a single item by value from an array. It returns true is the item is found and removed

We can pass in { deep: false } as the second argument of observable.array to prevent setting items to be observable recursively.

Also, we can pass in { name: "my array" } as the second argument to add a name for debugging purposes.

Conclusion

We can create Observable objects with the observable.object and the observable.array method to create Observable arrays.

We can watch for value changes with autorun which will give us the latest values as the observable object changes.

Also, we can set various options to control how the observable values will be converted.

Categories
Mobx

Creating Observables with Mobx

Mobx is a state management solution for JavaScript apps. It lets us observe values from a store and then set the values, which will be immediately reflected in the store.

In this article, we’ll look at how to create an Observable that lets us subscribe to it and then change the value of it.

The observable Function

The Mobx observable function takes an object. It’s also available in decorator form where we can set the value of it directly.

It can be used as follows:

  • observable(value)
  • @observable classProperty = value

It returns various kinds of values depending on what value is passed in. The value can be JavaScript primitive values, references, plain objects, class instances, arrays, and maps.

There’re some conversion rules applied to values that are passed into the observable function. They’re the following:

  • If value is an ES6 Map , then a new Observable Map will be returned. Observable maps are very useful if we want to react to changes in a specific entry, and addition or removal of entries.
  • If value is an ES6 Set , then a new Observable Set will be returned.
  • If value is an array, then a new Observable array will be returned.
  • If value is an object without a prototype, all its current properties will be made observable
  • If value is an object with a prototype, a JavaScript primitive or function, the observable will throw. We should use Boxed Observable observables instead.

For example, we can use observable as follows:

import { observable } from "mobx";
const map = observable.map({ foo: "value" });
map.observe(({ oldValue, newValue }) => console.log(oldValue, newValue));

map.set("foo", "new value");

In the code above, we have the observable.map function to create an Observable Map.

Then we attach a listener to it to watch the values with the observe function. The listener gives us an object with the oldValue and newValue properties so we can retrieve them.

When we call:

map.set("foo", "new value");

then we’ll see the values logged by the listener.

We can also do something similar with arrays:

import { observable } from "mobx";
const array = observable([1, 2, 3]);
array.observe(({ oldValue, newValue }) => console.log(oldValue, newValue));

array[1] = 5;

We passed in an array to the observable function which returns an Observable array. Then we can call observe on it to watch its values like before.

Then when we make changes to array , the listener then we pass into observe will log the new and old values.

To make primitives observable, we can use the box method as follows:

import { observable } from "mobx";
const num = observable.box(3);
num.observe(({ oldValue, newValue }) => console.log(oldValue, newValue));

num.set(10);

In the code above, we created an observable primitive with the box method. The code for observing the value change is the same as before.

Then we can use set to set a new value for the num Observable primitive.

The @observable Decorator

If our codebase has support for ES7 or TypeScript, then we can use the @observable decorator to make class properties observable.

We can use them as follows:

import { observable, computed } from "mobx";

class Person {
  @observable firstName = "Jane";
  @observable lastName = "Smith";

  @computed get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const person = new Person();
console.log(person.firstName);
console.log(person.lastName);
console.log(person.fullName);

In the code above, we created the Person class which has Observable properties as indicated by using the observable decorator. We also have a computed property fullName which is a getter.

Then we create a new Person instance and we can log the values.

We can also set the values by assigning a value as usual.

To use decorators, we can use a build system like Parcel and then add the @babel/core , @babel/plugin-proposal-class-properties, @babel/plugin-proposal-decorators , and @babel/preset-env packages and all them to .babelrc in the plugins section as follows:

{
  "presets": [
    "@babel/preset-env"
  ],
  "plugins": [
    [
      "@babel/plugin-proposal-decorators",
      {
        "legacy": true
      }
    ],
    [
      "@babel/plugin-proposal-class-properties",
      {
        "loose": true
      }
    ]
  ]
}

Conclusion

We can create Observable values with the observable object. It can be used with various kinds of objects as long as it’s used with the correct method to create an Observable value.

Then we can use the observe method to observe the value of the returned Observable and manipulate it.

We can also use the observable decorator with classes to create a class with Observable properties.

To use decorators we either use Babel or TypeScript.

Categories
Mobx

Simple State Management with Mobx

Mobx is a simpler alternative to state management for React apps. It works by creating a store and then watching it for changes, and we update the store by changing the values directly.

In this article, we’ll look at how to use Mobx and React together.

Installation

We install the mobx and mobx-react libraries to create our Mobx store and then connect it to our React component.

To do this, we run:

npm install mobx mobx-react

All modern browsers are supported by Mobx with version 4.x or earlier. Since version 5, Mobx uses proxies for state updates so Internet Explorer isn’t supported. It also works with React Native and Node.js

Basic Usage

To start using it, we create a store to hold the state. Then we can pass the store into our component.

For example, we can use Mobx to create a store then inject the store into our React component as follows:

import React from "react";
import ReactDOM from "react-dom";
import { observable } from "mobx";
import { observer } from "mobx-react";

class Count {
  @observable count = 0;
}

const App = observer(({ store }) => {
  return (
    <div className="App">
      <button
        onClick={() => {
          store.count++;
        }}
      >
        Increment
      </button>
      <button
        onClick={() => {
          store.count--;
        }}
      >
        Decrement
      </button>
      <p>{store.count}</p>
    </div>
  );
});

const store = new Count();
const rootElement = document.getElementById("root");
ReactDOM.render(<App store={store} />, rootElement);

In the code above, we created a store by defining the Count class:

class Count {
  @observable count = 0;
}

Then we create a new instance of the Count class so that we can pass it into our component as a prop by writing:

const store = new Count();

and:

ReactDOM.render(<App store={store} />, rootElement);

Then to define our App function component, we wrap a function around the observer function so that the latest values from the store will be observed.

This means that once we pass in the store , we’ll get the latest values automatically, and when we change the store properties that have the @observable decorator before it, the value will be propagated to the store.

Therefore, in our onClick props, we can pass in a function that changes store.count directly to update the values, which then will immediately be reflected in store.count that we added in the p element.

We can also use the observer decorator with React class component as follows:

import React from "react";
import ReactDOM from "react-dom";
import { observable, computed, autorun } from "mobx";
import { observer } from "mobx-react";

class Person {
  @observable firstName = "Jane";
  @observable lastName = "Smith";
  @computed
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

@observer
class App extends React.Component {
  render() {
    const { store } = this.props;
    return (
      <div className="App">
        <p>{store.fullName}</p>
      </div>
    );
  }
}

const store = new Person();
const rootElement = document.getElementById("root");
ReactDOM.render(<App store={store} />, rootElement);

It does the same thing as the observer function with React function components.

Computed Values and Getters

We can create a function in our store class that returns a value that’s computed from other observable values.

To do this, we can use the @computed decorator before our method. We can define one and use it as follows:

import React from "react";
import ReactDOM from "react-dom";
import { observable, computed } from "mobx";
import { observer } from "mobx-react";

class Person {
  @observable firstName = "Jane";
  @observable lastName = "Smith";
  @computed
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const App = observer(({ store }) => {
  return (
    <div className="App">
      <p>{store.fullName}</p>
    </div>
  );
});

const store = new Person();
const rootElement = document.getElementById("root");
ReactDOM.render(<App store={store} />, rootElement);

In the code above, we have a method fullName in the Person class that is a getter and it returns `${this.firstName} ${this.lastName}`, which are the 2 observable properties we defined combined.

Then we can get the fullname property in the store prop as we did in the App component.

Custom Reactions

Custom reactions are created by using the autorun, reaction, or when functions by passing in a callback to them. Reactions are used for committing side effects.

For example, we can create them as follows:

autorun(() => {
  console.log(store.fullName);
});

In the code above, we created a reaction that runs automatically when we run the code.

We log the value of store.fullName from above, and we can use that in our component as follows:

const App = observer(({ store }) => {
  autorun(() => {
    console.log(store.fullName);
  });

  return (
    <div className="App">
      <p>{store.fullName}</p>
    </div>
  );
});

Conclusion

We can use Mobx to create a simple store to store our values. It comes with decorators and functions to get us to watch the values from React components and manipulate the store values directly.

With the observable decorator, we can watch the values from the store and then save change the values directly.

The computed decorator lets us get the functions from getters in the store class in our components.

To commit side effects, we can add custom reactions.

Categories
Nuxt.js

Nuxt.js — Vuex

Nuxt.js is an app framework that’s based on Vue.js.

We can use it to create server-side rendered apps and static sites.

In this article, we’ll look at how to use Vuex with Nuxt.

Activate the Store

Vuex is built into Nuxt.

We can just import it and add the store option to the root Vue instance.

To add the store, we can write:

store/index.js

export const state = () => ({
  counter: 0
})

export const mutations = {
  increment(state) {
    state.counter++
  }
}

We created a root module for our Vuex store since the code is in the index.js file.

Then we can use it by writing:

page/counter.vue

<template>
  <div class="container">
    <button @click="increment">increment</button>
    <p>{{counter}}</p>
  </div>
</template>

<script>
export default {
  computed: {
    counter() {
      return this.$store.state.counter;
    },
  },
  methods: {
    increment() {
      this.$store.commit('increment');
    },
  },
};
</script>

We have access to the this.$store object.

The state property has the state.

And the commit method lets us run a mutation.

To create a namespaced module, we can change the name of the store file.

For example, we can create a store/counter.js file and write:

export const state = () => ({
  count: 0
})

export const mutations = {
  increment(state) {
    state.count++
  }
}

Then we can access the counter module by writing:

<template>
  <div class="container">
    <button @click="increment">increment</button>
    <p>{{count}}</p>
  </div>
</template>

<script>
import { mapMutations } from "vuex";

export default {
  computed: {
    count() {
      return this.$store.state.counter.count;
    },
  },
  methods: {
    increment() {
      this.$store.commit('counter/increment');
    },
  },
};
</script>

We add the namespace to access the state and commit our actions.

Also, we can use the map methods from Vuex to map the getters, mutations, and actions to properties in our component.

For example, we can write:

<template>
  <div class="container">
    <button @click="increment">increment</button>
    <p>{{count}}</p>
  </div>
</template>

<script>
import { mapMutations } from "vuex";

export default {
  computed: {
    count() {
      return this.$store.state.counter.count;
    },
  },
  methods: {
    ...mapMutations({
      increment: "counter/increment",
    }),
  },
};
</script>

to map our counter/increment mutation to the increment method with the mapMutations method.

Plugins

We can add Vuex plugins to our Vuex store.

For example, we can add the Vuex logger to our app by writing:

store/index.js

import createLogger from 'vuex/dist/logger'

export const plugins = [createLogger()]

export const state = () => ({
  count: 0
})

export const mutations = {
  increment(state) {
    state.count++
  }
}

We just export the plugins array to add our plugins.

The nuxtServerInit Action

The nuxtServerInit action is defined in the store.

This runs in any environment.

To use it, we can add it to our store by writing:

store/index.js

export const actions = {
  nuxtServerInit({ commit }, { req }) {
    commit('core/load', { foo: 'bar' })
  }
}

store/core.js

export const state = () => ({
  obj: {}
})

export const mutations = {
  load(state, payload) {
    state.obj = payload;
  }
}

foo.vue

<template>
  <div class="container">{{obj}}</div>
</template>

<script>
export default {
  computed: {
    obj() {
      return this.$store.state.core.obj;
    },
  },
};
</script>

We have the nuxtServerInit action in the root module.

It has the commit function to let us commit mutations.

req is the request object.

Conclusion

We can add a Vuex store to an Nuxt app with a few changes.

Categories
Nuxt.js

Nuxt.js — Plugins and Modules

Nuxt.js is an app framework that’s based on Vue.js.

We can use it to create server-side rendered apps and static sites.

In this article, we’ll look at how to use plugins on client and server-side environments and create modules.

Client or Server-Side Plugins

We can configure plugins to be only available on client or server-side.

One way to do this is to add client.js to the file name to create a client-side only plugin.

And we can add server.js to the file name to create a server-side only plugin.

To do this, in nuxt.config.js , we can write:

export default {
  plugins: [
    '~/plugins/foo.client.js',
    '~/plugins/bar.server.js',
    '~/plugins/baz.js'
  ]
}

If there’s no suffix, then the plugin is available in all environments.

We can do the same thing with the object syntax.

For example, we can write:

export default {
  plugins: [
    { src: '~/plugins/both-sides.js' },
    { src: '~/plugins/client-only.js', mode: 'client' },
    { src: '~/plugins/server-only.js', mode: 'server' }
  ]
}

The mode property can be set to 'client' to make the plugin available on the client-side.

To make a plugin available on server-side, we can set the mode to 'server' .

For plugins that are only available on server-side, we can check if process.server is true in the plugin code before we run the code.

Also, we can check if process.static is true before we run the plugin code on static pages.

Nuxt.js Modules

Nuxt.js comes with a few modules that we can use to extend Nuxt’s core functionality.

@nuxt/http is used to make HTTP requests.

@nuxt/content is used to write content and fetch Markdown, JSON, YAML, and CSV files through a MongoDB like API.

@nuxtjs/axios is a module used for Axios integration to make HTTP requests.

@nuxtjs/pwa is used to create PWAs.

@nuxtjs/auth is used for adding authentication.

Write a Module

We can create our own modules.

To add one, we can create a file in the modules folder.

For example, we can create a modules/simple.js file and write:

export default function SimpleModule(moduleOptions) {
  // ...
}

Then we can add the module into nuxt.config.js so that we can use it:

modules: [
  ['~/modules/simple', { token: '123' }]
],

Then object in the 2nd entry is passed into the SimpleModule function as its argument.

Modules may be async.

Build-only Modules

We can create build-only modules and put them in the buildModules array in nuxt.config.js .

For example, we can write:

modules/async.js

import fse from 'fs-extra'

export default async function asyncModule() {
  const pages = await fse.readJson('./pages.json')
  console.log(pages);
}

We added the fs-extra module to read files.

The function is async, so it returns a promise with the resolved value being what we return.

In nuxt.config.js , we add:

buildModules: [
  '~/modules/async'
],

to add our module.

The module will be loaded when we run our dev server or at build time.

Conclusion

We can create modules and plugins that are available on the client or server-side with Nuxt.