Categories
JavaScript React

Watching MobX Observables with the Reaction Function

We can create Observable objects with MobX. To watch it for changes, we can use the reaction function.

In this article, we’ll look at how to use the reaction function to watch for value changes in MobX Observables.

Reaction

We can use the reaction function as follows:

reaction(() => data, (data, reaction) => { sideEffect }, options?)

The reaction function is a variation on autorun that gives more fine-grained control on how an observable will be tracked.

The side-effect won’t be run directly when the observable is created. It’ll run only after the data expression returns a new value for the first time.

Any observables that are accessed while executing the side effect won’t be tracked.

reaction returns a disposer function.

The second function passed to reaction will retrieve 2 arguments when it’s run. The first argument is the value returned by the data function. The second argument is the current reaction during execution.

The side effect only reacts to data that was accessed in the data expression. It’ll only be triggered when the data returned by the expression has changed. This means that we have to produce things we need on our side effects.

Options

The third argument of reaction is an options object that can take the following optional options:

  • fireImmediately — boolean that indicates the effect function should trigger immediately after the first run of the data function. This is false by default.
  • delay — number of milliseconds that can be used to debounce the effect function. If it’s zero then no debouncing will happen
  • equals — a compare function that’ll be used to compare the previous and next values produced by the data function. The effect function will only be invoked if the function returns false . Default is comparer.default .
  • name — a string that’s used as the name for the reaction
  • onError — a function that’ll handle the errors of this reaction, rather than propagating them
  • scheduler — set a custom scheduler to determine how re-running the autorun function should be scheduled

Usage

We can use the reaction function as follows:

import { reaction, observable } from "mobx";
const todos = observable([  
  {  
    title: "eat"  
  },  
  {  
    title: "drink"  
  }  
]);

reaction(  
  () => todos.map(todo => todo.title),  
  titles => console.log(titles.join(", "))  
);

todos.push({ title: "sleep" });

In the code above, we created a new observable array using the observable function, which we assigned to the todos constant.

Then we call the reaction function, where we pass a callback to return an array which has the title strings from the todos observable array as the first argument.

In the second argument, we get the titles array returned from the function in the first argument, and the console.log the titles array joined together with join .

Then we call push on it, as we did in the last line, the new comma-separated string will be logged with the console.log since we changed it. It won’t log the value when it’s initially created.

Conclusion

We can use the reaction function to watch for observable variable changes.

It won’t watch for the value when it’s initially created.

reaction takes a callback to watch the value and return something we want from it. The second argument takes a value that takes the returned value from the first function and then we can perform some side effects on it.

The 3rd argument takes an object that takes a variety of options.

Categories
JavaScript React

Lifting State in React Components

React is a library for creating front end views. It has a big ecosystem of libraries that work with it. Also, we can use it to enhance existing apps.

In this article, we’ll look at the principle of lifting states up.

Lifting State Up

We should lift any shared state up to their closest common ancestor.

This way, one state can be shared between multiple child components by passing them down via props.

For example, if we want to build a calculator for converting lengths, we can write the following:

import React from "react";  
import ReactDOM from "react-dom";

class LengthInput extends React.Component {  
  constructor(props) {  
    super(props);  
    const { length } = this.props;  
    this.state = { length };  
  }  
  
  handleChange(e) {  
    this.props.onChange(e);  
  } 

  componentWillReceiveProps(props) {  
    const { length } = props;  
    this.setState({ length });  
  } 

  render() {  
    const { unit } = this.props;  
    return (  
      <>  
        <label>Length ({unit})</label>  
        <input  
          value={this.state.length}  
          onChange={this.handleChange.bind(this)}  
          placeholder={this.props.unit}  
        />  
        <br />  
      </>  
    );  
  }  
}

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = { lengthInMeter: 0, lengthInFeet: 0 };  
  } 

  handleMeterChange(e) {  
    this.setState({ lengthInMeter: +e.target.value });  
    this.setState({ lengthInFeet: +e.target.value \* 3.28 });  
  } 

  handleFeetChange(e) {  
    this.setState({ lengthInFeet: +e.target.value });  
    this.setState({ lengthInMeter: +e.target.value / 3.28 });  
  } 

  render() {  
    return (  
      <div className="App">  
        <LengthInput  
          length={this.state.lengthInMeter}  
          onChange={this.handleMeterChange.bind(this)}  
          unit="meter"  
        />  
        <LengthInput  
          length={this.state.lengthInFeet}  
          onChange={this.handleFeetChange.bind(this)}  
          unit="feet"  
        />  
      </div>  
    );  
  }  
}

const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

In the code above, we have a length converter than converts meters to feet when we’re typing in the meters field and vice versa.

What we’re doing is that we keep the lengths all in the App component. This is why the principle is called lifting the states up.

Since we’re keeping the lengths in the App component, we have to pass them down to the LengthInput components.

To do that, we pass props to them. Also, we pass in the units, and the change handler functions down to our LengthInput components, so that they can the functions to update the states in the App component.

In the handleFeetChange and handleMeterChange functions, we set the state according to the values entered in the LengthInput components.

We call this.setState in both functions to set the states. Each time setState is called, render will be called, so that the latest state will be passed down to our LengthInput components.

In the LengthInput components, we have the componentWillReceiveProps hook which will get the latest prop values and then set the length state accordingly.

this.state.length is set as the value of the input elements in LengthInput , so the inputted value will be shown.

The advantage of lifting the states up to the parent component is that we only have to keep one copy of the state. Also, we don’t have to repeat the processing of the states in different child components.

The values of the inputs stay in sync because they’re computed from the same state.

Conclusion

When writing React apps, it’s recommended that we move shared states between child components into their parent component. This is called lifting the states up.

We want to do this so that we don’t have to duplicate the processing of data in child components, and we only have a single source of truth.

To do this, we can pass in data and function as props down to child components. This way, functions from the parent component can be called from child components.

Then to update the states of child component with the latest values passed from the props, we can use the componentWillReceiveProps hook to update the states from the props.

Categories
JavaScript Vue

Vue.js Components — Components Registration

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to register Vue.js components globally and locally.

Component Registration

We can register a Vue component by using the Vue.component method.

It takes 2 arguments. The first is the name of the component as a string. The second argument is the object with various options.

The recommended name style for components with multiple words is kebab case or PascalCase.

However, only names with kebab case are valid for directly embedding in the DOM.

We can define a kebab case component as follows:

Vue.component('component-name', { /* ... */ })

A PascalCase name can be defined as follows:

Vue.component('ComponentName', { /* ... */ })

Global Registration

We register components globally with the Vue.component method. Components that are registered globally are available everywhere.

For example, we can use it as follows:

src/index.js :

Vue.component("component-a", { template: `<div>A</div>` });  
Vue.component("component-b", { template: `<div>B</div>` });
new Vue({  
  el: "#app"  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <component-a></component-a>  
      <component-b></component-b>  
    </div> <script src="src/index.js"></script>  
  </body>  
</html>

Then we get A and B displayed.

The component are available, so we can nest component-a inside component-b , or component-b inside component-a or any other combination we can think of.

Local Registration

We don’t want to globally register components all the time since we don’t want to include them everywhere.

Including them globally means that the code will bloat because they’re included even though it isn’t being used.

Instead of calling Vue.component , we can define them as plain JavaScript objects and included in the components property of a component as follows:

src/index.js :

const ComponentA = { template: `<div>A</div>` };  
const ComponentB = { template: `<div>B</div>` };
new Vue({  
  el: "#app",  
  components: {  
    "component-a": ComponentA,  
    "component-b": ComponentB  
  }  
});

In the components object, “component-a” and “component-b” are the component names and ComponentA and ComponentB are the components.

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <component-a></component-a>  
      <component-b></component-b>  
    </div> <script src="src/index.js"></script>  
  </body>  
</html>

We included the components we defined in the src/index.js in the template above.

Locally registered components aren’t available in subcomponents. It’s only available in the component that it’s registered in.

If we want them to be available in other components, we have to register them again.

Using Modules

If we want to register modules locally, we can move them to their own module and export them. Then they can be imported to wherever they have to register.

For example, we can create a new file called component.js and write the following code:

src/component.js :

export const ComponentA = { template: `<div>A</div>` };  
export const ComponentB = { template: `<div>B</div>` };

Then we can import it in index.js :

import { ComponentA, ComponentB } from "./components";
new Vue({  
  el: "#app",  
  components: {  
    "component-a": ComponentA,  
    "component-b": ComponentB  
  }  
});

And index.html is kept the same as before:

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <component-a></component-a>  
      <component-b></component-b>  
    </div> <script src="src/index.js"></script>  
  </body>  
</html>

We can also use export default instead of export as follows:

src/component.js :

const ComponentA = { template: `<div>A</div>` };  
export default ComponentA;

src/index.js :

import ComponentA from "./componentA";
new Vue({  
  el: "#app",  
  components: {  
    "component-a": ComponentA  
  }  
});

With default export, we can name ComponentA anything we like when we import it.

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <component-a></component-a>  
    </div> <script src="src/index.js"></script>  
  </body>  
</html>

Conclusion

In Vue.js, we can register components globally and locally. Global registration makes a module available for everything.

We can register components globally by using the Vue.component method.

Local registration only makes it available to the component it’s registered in.

Locally registered components should be used more often because they won’t be included when it’s not used in the final build.

Categories
JavaScript Vue

Introduction to Vue.js Render Functions

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to define and use Vue.js render functions.

Basics

We can define and use render functions to programmatically display elements in our Vue app.

An example that can benefit from using render functions are components where there’re lots of v-if cases.

For example, if we want to display headings of various sizes with one component, we can define the following component:

src/index.js :

Vue.component("variable-heading", {  
  template: "#variable-heading-template",  
  props: {  
    size: {  
      type: Number,  
      required: true  
    }  
  }  
});

new Vue({  
  el: "#app"  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <script type="text/x-template" id="variable-heading-template">  
      <h1 v-if="size === 1">  
        <slot></slot>  
      </h1>  
      <h2 v-else-if="size === 2">  
        <slot></slot>  
      </h2>  
      <h3 v-else-if="size === 3">  
        <slot></slot>  
      </h3>  
      <h4 v-else-if="size === 4">  
        <slot></slot>  
      </h4>  
      <h5 v-else-if="size === 5">  
        <slot></slot>  
      </h5>  
      <h6 v-else-if="size === 6">  
        <slot></slot>  
      </h6>  
    </script>  
    <div id="app">  
      <variable-heading :size="1">foo</variable-heading>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

The code above works by passing in the size value to the variable-heading component as a prop and then the v-if in component template will determine which element to display depending on the size prop’s value.

It’s very long and has duplicate slot elements.

We can use render functions to reduce the code above to:

src/index.js :

Vue.component("variable-heading", {  
  props: {  
    size: {  
      type: Number,  
      required: true  
    }  
  },  
  render(createElement) {  
    return createElement(`h${this.size}`, this.$slots.default);  
  }  
});

new Vue({  
  el: "#app"  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <variable-heading :size="1">foo</variable-heading>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

The code above works by us adding a render method to our variable-heading component, which returns a Virtual DOM node, or VNode for short, by calling createElement function with the tag name as the first argument, and the children HTML nodes as an array as the second argument.

A Virtual DOM Node tells Vue what to render on the screen, which will be turned into a real DOM node.

The this.$slot.default have the child HTML nodes passed in between the opening and closing tags of variable-heading .

Nodes, Trees, and the Virtual DOM

HTML code are rendering dynamically by rendering a dynamic tree of elements called the DOM tree. DOM standards for the Document Object Model, which abstracts the HTML structure into a tree.

A tree has nodes and some nodes like text and HTML nodes are displayed in our browsers’ screens.

Vue manipulates and renders the DOM tree for us so that we don’t have to do it ourselves.

For example, we can create a component with a render function as follows:

Vue.component("variable-heading", {  
  data() {  
    return { msg: "hi" };  
  },  
  render(createElement) {  
    return createElement("p", this.msg);  
  }  
});

createElement Arguments

The createElement takes a few arguments.

The first argument is the HTML tag name, component options or async function that resolves to one of the 2 kinds of items mentioned before. It’s required.

The second argument is an object that has data corresponding to the attributes that we want to add to the template. It’s an optional argument.

The third and last argument is a string or array of the child VNodes built using createElement . This is also optional.

The Data Object

The data object has many properties that can control the attributes, classes, event handlers, inline styles of the created VNode.

The following properties are available:

class

It’s an object which has the class names that we want to include in the class attribute of the HTML element we render.

For example, we can write the following:

class: {  
  foo: true,  
  bar: false  
}

Then the foo class is included in the class attribute and bar isn’t.

style

An object with the styles that we want to include. For example, we can write the following:

style: {  
  color: 'yello',  
  fontSize: '12px'  
},

Then the styles above will be included with the element that’s rendered.

attrs

attrs object has the attributes and values that we want to include in the rendred HTML element. For example, we can write:

attrs: {  
  id: 'foo'  
}

Then our rendered HTML element will have the ID foo .

props

The props object has the component’s props with the corresponding value. For instance, we can write:

props: {  
  foo: 'bar'  
}

Then we can pass in the foo prop with the value 'bar' .

domProps

domProps object have the DOM properties that we want to pass in. For example, we can write:

domProps: {  
  innerHTML: 'foo'  
}

Then our rendered HTML element has the text foo inside.

on

With the on object, we can set event handlers for our rendered HTML element. It’s used for listening to events that are emitted via $emit .

For example, we can write:

on: {  
  click: this.clickHandler  
}

to listen to Vue click events.

nativeOn

We can listen to native browser events with the nativeOn object. It haves the same object as on expect that we have native event handlers instead of Vue event handlers.

directives

We can pass in the directives array to pass in one or more directives with their values, arguments, modifiers, etc.

For example, we can write:

directives: [  
  {  
    name: 'custom-directive',  
    value: '2',  
    expression: '1 + 1',  
    arg: 'foo',  
    modifiers: {  
      bar: true  
    }  
  }  
],

Then we can apply the custom-directive directive with modifiers, arguments, values, and expressions into the VNode.

scopedSlots and slot

We can pass in slots into our directive by setting it to a function that returns an element.

For scoped slots, we can write:

scopedSlots: {  
  default: props => createElement('span', props.text)  
}

For slots, we can just set the value as the name of the slot.

Other special top-level properties

key takes the key, ref takes a string ref name, refInFor is a boolean that is true if the same ref name is applied to multiple element.

Full Example

We can set the properties above as in the example below:

src/index.js :

Vue.component("anchor-text-block", {  
  render(createElement) {  
    const [slot] = this.$slots.default;  
    const headingId = slot.text.toLowerCase().replace(/ /g, "-");return createElement("div", [  
      createElement(  
        "a",  
        {  
          attrs: {  
            name: headingId,  
            href: `#${headingId}`  
          }  
        },  
        this.$slots.default  
      ),  
      createElement(  
        "p",  
        {  
          style: {  
            color: "green",  
            fontSize: this.fontSize  
          }  
        },  
        this.paragraphText  
      )  
    ]);  
  },  
  props: {  
    fontSize: {  
      type: Number,  
      required: true  
    },  
    paragraphText: {  
      type: String,  
      required: true  
    }  
  }  
});

new Vue({  
  el: "#app"  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <anchor-text-block paragraph-text="foo" :font-size="12"  
        >link</anchor-text-block  
      >  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we created an anchor-text-block component that 2 props, paragraph-text and font-size as indicated in the props property that we have in the component’s code.

The kebab case in the template is automatically mapped to camelCase in the JavaScript code.

In the render method, we called createElement , which creates a div , and then in the second argument, we have an array with the a and p elements created. This is where we passed in both props.

We also set the attrs property to #link since we have link as the slot’s inner text since that’s what we added between the opening and closing tags.

In the p element, we set the fontSize style from the prop and set the color to green.

Conclusion

We can use render functions to create components that are more flexible than our usual components.

It can have everything that components usually have like event handlers, directives, styles, attributes, nested elements and components and more.

Categories
JavaScript Vue

Vue.js Render Functions — Advanced Features

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at more advanced features of render functions.

A render function is a method of a component that gives us an alternative way to create HTML elements via the creation of Virtual DOM Nodes.

VNodes Must Be Unique

VNodes must be unique in the array that we pass into the second argument of the VNode function.

We can’t create VNodes that have duplicate child elements. For example, the following is invalid:

Vue.component("text-block", {  
  render(createElement) {  
    const dupNode = createElement("p", "hi");  
    return createElement("div", [dupNode, dupNode]);  
  }  
});

…since we have 2 dupNode in the array.

Instead, we should write:

Vue.component("text-block", {  
  render(createElement) {  
    return createElement(  
      "div",  
      Array.apply(null, { length: 5 }).map(() => {  
        return createElement("p", "hi");  
      })  
    );  
  }  
});

to call createElement multiple times to create fresh VNodes with the same content repeatedly.

Replacing Template Features with Plain JavaScript

We can replace v-if with if statements and v-for with map .

For example, we can create a person component as follows:

src/index.js:

Vue.component("persons", {  
  props: ["persons"],  
  render(createElement) {  
    if (this.persons.length) {  
      return createElement(  
        "ul",  
        this.persons.map(p => {  
          return createElement("li", p);  
        })  
      );  
    } else {  
      return createElement("p", "Persons list is empty");  
    }  
  }  
});

new Vue({  
  el: "#app",  
  data: {  
    persons: ["Joe", "Jane", "Mary"]  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="[https://cdn.jsdelivr.net/npm/vue/dist/vue.js](https://cdn.jsdelivr.net/npm/vue/dist/vue.js)"></script>  
  </head>  
  <body>  
    <div id="app">  
      <persons :persons="persons"></persons>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we get a list of persons rendered.

This is the same as:

src/index.js :

Vue.component("persons", {  
  props: ["persons"],  
  template: `      
    <ul v-if='persons.length'>  
      <li v-for='p of persons'>{{p}}</li>  
    </ul>  
    <p v-else>Persons list is empty</p>  
  `  
});

new Vue({  
  el: "#app",  
  data: {  
    persons: ["Joe", "Jane", "Mary"]  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <persons :persons="persons"></persons>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

v-model

There’s no equivalent to v-model in render functions. We can implement equivalent logic as follows:

src/index.js :

Vue.component("custom-input", {  
  props: ["value"],  
  render(createElement) {  
    return createElement("input", {  
      domProps: {  
        value: this.value  
      },  
      on: {  
        input: event => {  
          this.$emit("input", event.target.value);  
        }  
      }  
    });  
  }  
});

new Vue({  
  el: "#app",  
  data: {  
    msg: ""  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <custom-input v-model="msg"></custom-input>  
      {{msg}}  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we emit the input event in our custom-input component and takes the value prop. This is the same as v-model .

Therefore, we can bind to a model of the parent with v-model and when we type something into the input, the typed in data will show below the input.

Event & Key Modifiers

To add .passive, .capture and .once event modifiers, we can prefix the events handlers with the following prefixes to add the modifiers to our event handlers:

  • .passive — prefix with &
  • .capture — prefix with !
  • .once — prefix with ~
  • .capture.once or .once.capture — prefix with ~!

For example, we can write:

on: {  
  '!click': this.doThisInCapturingMode,  
  '~keyup': this.doThisOnce,  
  '~!mouseover': this.doThisOnceInCapturingMode  
}

Other modifiers don’t need a prefix since they have equivalents in plain JavaScript. They’re the following:

  • .stop — use event.stopPropagation()
  • .prevent — use event.preventDefault()
  • .self — use if (event.target !== event.currentTarget) return
  • Keys — e.g. .enter, or .13 — use if (event.keyCode !== 13) { //... }
  • Modifiers Keys — e.g. .ctrl, .alt, .shift, .meta — use if (!event.ctrlKey) { //... }

For example, we can add a keyup event handler as follows:

Vue.component("custom-input", {  
  props: ["value"],  
  render(createElement) {  
    return createElement("input", {  
      domProps: {  
        value: this.value  
      },  
      on: {  
        keyup: event => {  
          event.preventDefault();  
          this.$emit("input", event.target.value);  
        }  
      }  
    });  
  }  
});

Then the input event is emitted when a keyboard key is released.

We can use the modifiers that have special prefixes as follows:

src/index.js :

Vue.component("custom-button", {  
  render(createElement) {  
    return createElement("button", {  
      domProps: {  
        innerHTML: "Click Me",  
        id: "child"  
      },  
      on: {  
        "!click": event => {  
          alert(`${event.target.id} clicked`);  
        }  
      }  
    });  
  }  
});

new Vue({  
  el: "#app",  
  methods: {  
    onClick() {  
      alert(`parent clicked.`);  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <custom-button></custom-button>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we get child clicked alert popup displayed since we used the capture modifier on click.

Conclusion

If we want to write components with v-if and v-for but using render functions, we can replace them with if statements and array’s map method respectively.

For adding Vue event handlers, we can add modifiers to some of them with special prefixes and others by using plain JavaScript.

Also, we can add duplicate elements in an array of child VNodes.