Hírek, események

Reinventing Hooks with React Easy State

Although I use React Hooks a lot, I don’t really like them. They are solving tough problems, but with an alien API that is hard to manage at scale.

It’s even harder to wire them together with a library that is based on mutable data. The two concepts don’t play well together, and forcing them would cause a hot mess. Instead, the React Easy State team at RisingStack is working on alternative patterns that combine the core values of React Hooks and mutable data.

We think these core values are:

  • encapsulation of pure logic,
  • reusability,
  • and composability.

At the same time, we are trying to get rid of:

  • the strange API,
  • reliance on closures to store data,
  • and overused patterns.

This article guides you through these points and how React Easy State tackles them compared to vanilla Hooks.

In case you’re not familiar with React Easy State, check out our introductory blogpost.

TLDR: “React Easy State is a transparent reactivity based state manager for React. In practical terms: it automagically decides when to render which components without explicit orders from you.”

A basic example of Hooks & React Easy State

Let’s see how to set the document title with Hooks and with React Easy State.

react easy state title doc

The Hooks version

import React, { useState, useCallback, useEffect } from "react";

export default () => {
  const [title, setTitle] = useState("App title");
  const onChange = useCallback(ev => setTitle(ev.target.value), [setTitle]);

  useEffect(() => {
    document.title = title;
  }, [title]);

  return <input value={title} onChange={onChange} />;
};

CodeSandbox demo

The React Easy State version

import React from "react";
import { view, store, autoEffect } from "@risingstack/react-easy-state";

export default view(() => {
  const title = store({
    value: "App title",
    onChange: ev => (title.value = ev.target.value)
  });

  autoEffect(() => (document.title = title.value));

  return <input value={title.value} onChange={title.onChange} />;
});

CodeSandbox demo

autoEffect replaces the useEffect hook while store replaces useStateuseCallbackuseMemo and others. Under the hood, they are built on top of React hooks, but they utilize a significantly different API and mindset.

Reusability

What if you have to set the document’s title again for other pages? Having to repeat the same code every time would be disappointing. Luckily, Hooks were designed to capture reusable logic.

Hooks version

useTitle.js:

import { useState, useCallback, useEffect } from "react";

export default function useTitle(initalTitle) {
  const [title, setTitle] = useState(initalTitle);
  const onChange = useCallback(ev => setTitle(ev.target.value), [setTitle]);

  useEffect(() => {
    document.title = title;
  }, [title]);

  return [title, onChange];
}

App.js:

import React from "react";
import useTitle from "./useTitle";

export default () => {
  const [title, onChange] = useTitle();
  return <input value={title} onChange={onChange} />;
};

CodeSandbox demo

React Easy State version

React Easy State tackles the same problem with store factories: a store factory is a function that returns a store. There are no other rules. You can use store and autoEffect – among other things – inside it.

titleStore.js:

import { store, autoEffect } from "@risingstack/react-easy-state";

export default function titleStore(initalTitle) {
  const title = store({
    value: initalTitle,
    onChange: ev => (title.value = ev.target.value)
  });

  autoEffect(() => (document.title = title.value));

  return title;
}

App.js:

import React from "react";
import { view } from "@risingstack/react-easy-state";
import titleStore from "./titleStore";

export default view(() => {
  const title = titleStore("App title");
  return <input value={title.value} onChange={title.onChange} />;
});

CodeSandbox demo

Closures and Dependency Arrays

Things can get messy as complexity grows, especially when async code gets involved. Let’s write some reusable data fetching logic! Maybe we will need it later (;

Hooks version

useFetch.js:

import { useState, useCallback } from "react";

export default function useFetch(baseURL) {
  const [state, setState] = useState({});

  const fetch = useCallback(
    async path => {
      setState({ loading: true });
      try {
        const data = await fetchJSON(baseURL + path);
        setState({ ...state, data, error: undefined });
      } catch (error) {
        setState({ ...state, error });
      } finally {
        setState(state => ({ ...state, loading: false }));
      }
    },
    [baseURL, state]
  );

  return [state, fetch];
}

App.js:

import React from "react";
import useFetch from "./useFetch";

const POKE_API = "https://pokeapi.co/api/v2/pokemon/";

export default () => {
  const [{ data, error, loading }, fetch] = useFetch(POKE_API);

  return (
    <>
      <button onClick={() => fetch("ditto")}>Fetch pokemon</button>
      <div>
        {loading ? "Loading ..." : error ? "Error!" : JSON.stringify(data)}
      </div>
    </>
  );
};

CodeSandbox demo

Notice how we have to use a setState with an updater function in the finally block of useFetchDo you know why does it need special handling?

  • If not, try to rewrite it to setState({ ...state, loading: false }) in the CodeSandbox demo and see what happens. Then read this article to gain a deeper understanding of hooks and stale closures. Seriously, do these before you go on!
  • Otherwise, try to think of a good reason why the other setStates should be rewritten to use updater functions. (Keep reading for the answer.)

React Easy State version

You have probably heard that mutable data is bad (like a 1000 times) over your career. Well… closures are worse. They seem simple at a glance but they hide your data in function creation time specific “pockets” that introduce a new layer of complexity. Instead of using the latest data during a function execution you have to remember what data it “pocketed” when it was created.

Hooks are heavily relying on closures to store data, which leads to issues like the example above. Obviously, this is not a bug in the hooks API, but it is a serious cognitive overhead that gets mind-bending as your complexity grows.

React Easy State is storing its data in mutable objects instead, which has its own quirks, but it is way easier to handle in practice. You will always get what you ask for, and not some stale data from a long-gone render.

fetchStore.js:

import { store } from "@risingstack/react-easy-state";

export default function fetchStore(baseURL) {
  const resource = store({
    async fetch(path) {
      resource.loading = true;
      try {
        resource.data = await fetchJSON(baseURL + path);
        resource.error = undefined;
      } catch (error) {
        resource.error = error;
      } finally {
        resource.loading = false;
      }
    }
  });

  return resource;
}

App.js:

import React from "react";
import { view } from "@risingstack/react-easy-state";
import fetchStore from "./fetchStore";

const POKE_API = "https://pokeapi.co/api/v2/pokemon/";

export default view(() => {
  const { loading, data, error, fetch } = fetchStore(POKE_API);

  return (
    <>
      <button onClick={() => fetch("ditto")}>Fetch pokemon</button>
      <div>
        {loading ? "Loading ..." : error ? "Error!" : JSON.stringify(data)}
      </div>
    </>
  );
});

CodeSandbox demo

Composability

While we played with fetching data, the document title setting application turned into a massive hit with tons of feature requests. Eventually, you end up fetching related pokemon from the free pokeAPI.

Luckily you already have a data fetching hook, what a coincidence…

You don’t want to refactor your existing code snippets, and it would be nicer to compose them together into more complex units. The hooks API was designed to handle this.

Hooks version

usePokemon.js:

import { useEffect } from "react";
import useTitle from "./useTitle";
import useFetch from "./useFetch";

const POKE_API = "https://pokeapi.co/api/v2/pokemon/";

export default function usePokemon(initialName) {
  const [name, onNameChange] = useTitle(initialName);
  const [data, fetch] = useFetch(POKE_API);

  useEffect(() => {
    fetch(name);
  }, [fetch, name]);

  return { ...data, name, onNameChange };
}

App.js:

import React from "react";
import usePokemon from "./usePokemon";

export default () => {
  const pokemon = usePokemon("ditto");

  return (
    <>
      <input value={pokemon.name} onChange={pokemon.onNameChange} />
      <div>
        {pokemon.loading
          ? "Loading ..."
          : pokemon.error
          ? "Error!"
          : JSON.stringify(pokemon.data)}
      </div>
    </>
  );
};

CodeSandbox demo

This example has a serious but hard to grasp flaw – an infinite loop – caused by the long-forgotten useFetch hook.

Otherwise try to think of a good reason why the other setStates should be rewritten to use updater functions. (Keep reading for the answer.)

— Me, a paragraph ago

So you kept reading, and it’s finally answer time!

Let’s take a closer look at useFetch again.

useFetch.js part:

const [state, setState] = useState({});

const fetch = useCallback(
  async path => {
    setState({ loading: true });
    try {
      const data = await fetchJSON(baseURL + path);
      setState({ ...state, data, error: undefined });
    } catch (error) {
      setState({ ...state, error });
    } finally {
      setState(state => ({ ...state, loading: false }));
    }
  },
  [baseURL, state]
);

The fetch callback uses state and has it inside its dependency array. This means that whenever state changes fetch gets recreated, and whenever fetch gets recreated our useEffect in usePokemon kicks in …

useEffect(() => {
  fetch(name);
}, [fetch, name]);

That’s bad news! We only want to refetch the pokemon when name changes. It’s time to remove fetch from the dependency array.

And it breaks again… This time, it is not looping, but it always fetches the first (stale) pokemon. We keep using an old fetch that is stuck with a stale closure as its data source.

The correct solution is to modify our useFetch hook to use the setState function inside the fetch callback and remove the state dependency from its dependency array.

This mess is caused by the combination of closures and hook dependency arrays. Let’s avoid both of them.

React Easy State version

React Easy State takes a different approach to composability. Stores are simple objects which can be combined by nesting them in other objects.

pokeStore.js:

import { store, autoEffect } from "@risingstack/react-easy-state";
import titleStore from "./titleStore";
import fetchStore from "./fetchStore";

const POKE_API = "https://pokeapi.co/api/v2/pokemon/";

export default function pokeStore(initialName) {
  const pokemon = store({
    name: titleStore(initialName),
    data: fetchStore(POKE_API)
  });

  autoEffect(() => pokemon.data.fetch(pokemon.name.value));

  return pokemon;
}

App.js:

import React from "react";
import { view } from "@risingstack/react-easy-state";
import pokeStore from "./pokeStore";

export default view(() => {
  const pokemon = pokeStore("ditto");

  return (
    <>
      <input value={pokemon.name.value} onChange={pokemon.name.onChange} />
      <div>
        {pokemon.data.loading
          ? "Loading ..."
          : pokemon.data.error
          ? "Error!"
          : JSON.stringify(pokemon.data.data)}
      </div>
    </>
  );
});

CodeSandbox demo

The data is stored in – always fresh – mutable objects and hook-like dependency arrays are not required because of the underlying transparent reactivity. Our original fetchStore works without any modification.

Extra Features that Hooks don’t have

React Easy State is a state management library, not a hook alternative. It provides some features that Hooks can not.

Global state

You can turn any local state into a global one by moving it outside of component scope. Global state can be shared between components regardless of their relative position to each other.

pokemon.js:

import pokeStore from "./pokeStore";

// this global state can be used by any component
export default pokeStore("ditto");

Input.js:

import React from "react";
import { view } from "@risingstack/react-easy-state";
import pokemon from "./pokemon";

export default view(() => (
  <input value={pokemon.name.value} onChange={pokemon.name.onChange} />
));

Display.js:

import React from "react";
import { view } from "@risingstack/react-easy-state";
import pokemon from "./pokemon";

export default view(() => (
  <div>
    {pokemon.data.loading
      ? "Loading ..."
      : pokemon.data.error
      ? "Error!"
      : JSON.stringify(pokemon.data.data)}
  </div>
));

App.js:

import React from "react";
import { view } from "@risingstack/react-easy-state";
import Input from "./Input";
import Display from "./Display";

export default view(() => (
  <>
    <Input />
    <Display />
  </>
));

CodeSandbox demo

As you can see, old-school prop propagation and dependency injection is replaced by simply importing and using the store.

How does this affect testability, though?

Testing

Hooks encapsulate pure logic, but they can not be tested as such. You must wrap them into components and simulate user interactions to access their logic. Ideally, this is fine since you want to test everything – logic and components alike. Practically, time constraints of real-life projects won’t allow that. I usually test my logic and leave my components alone.

React Easy State store factories return simple objects, which can be tested as such.

fetchStore.test.js:

import fetchStore from "./fetchStore";

describe("fetchStore", () => {
  const TEST_URL = "https://test.com/";
  let fetchMock;

  beforeAll(() => {
    fetchMock = jest
      .spyOn(global, "fetch")
      .mockReturnValue(Promise.resolve({ json: () => "Some data" }));
  });
  afterAll(() => {
    fetchMock.mockRestore();
  });

  test("should fetch the required resource", async () => {
    const resource = fetchStore(TEST_URL);

    const fetchPromise = resource.fetch("resource");
    expect(resource.loading).toBe(true);
    expect(fetchMock).toBeCalledWith("https://test.com/resource");
    await fetchPromise;
    expect(resource.loading).toBe(false);
    expect(resource.data).toBe("Some data");
  });
});

CodeSandbox demo

Class components

While hooks are new primitives for function components only, store factories work regardless of where they are consumed. This is how you can use our pokeStore in a class component.

App.js:

import React, { Component } from "react";
import { view } from "@risingstack/react-easy-state";
import pokeStore from "./pokeStore";

class App extends Component {
  pokemon = pokeStore("ditto");

  render() {
    return (
      <>
        <input
          value={this.pokemon.name.value}
          onChange={this.pokemon.name.onChange}
        />
        <div>
          {this.pokemon.data.loading
            ? "Loading ..."
            : this.pokemon.data.error
            ? "Error!"
            : JSON.stringify(this.pokemon.data.data)}
        </div>
      </>
    );
  }
}

export default view(App);

CodeSandbox demo

Using store factories in classes still has a few rough edges regarding autoEffect cleanup, we will address these in the coming releases.

Reality check

This article defied a lot of trending patterns, like:

  • hooks,
  • avoiding mutable data,
  • traditional dependency injection,
  • and full front-end testing.

While I think all of the above patterns need a revisit, the provided alternatives are not guaranteed to be ‘better’. React Easy State has its own rough edges, and we are working hard to soften them in the coming releases.

As a starter, keep tuned for our ‘Idiomatic React Easy State’ docs in the near future. Consider this article as a fun and thought-provoking experiment in the meantime.

The important thing is to not stop questioning. Curiosity has its own reason for existing.

— Albert Einstein

Introducing React Easy State by RisingStack

Hi! About a year ago I created React Easy State – a moderately popular React state manager – which currently has around 1.8K stars, and a small but enthusiastic community forming around it. Unfortunately, I didn’t have enough time to keep up with the blooming community in the last couple of months.

I’m happy to announce, that this situation ends today!

React Easy State just got moved under RisingStack and receives company support from now on. The new, enthusiastic support team without licensing changes makes me really excited about the future!

Special shoutout to my colleauges, Roland SzokePeter Czibik and Daniel Gergely who already contributed immensely to this project in the past weeks! <3

react-easy-state-by-risingstack-logo

So what is React Easy State?

React Easy State is a transparent reactivity based state manager for React. In practical terms: it automagically decides when to render which components without explicit orders from you.

import React from 'react';
import { store, view } from 'react-easy-state';

const counter = store({
  num: 0,
  increment: () => counter.num++
});

// this component re-render's when counter.num changes
export default view(() => (
  <button onClick={counter.increment}>{counter.num}</button>
));

Why should I use it?

Transparent reactivity is not a new idea, Vue and React’s Mobx are popular libraries that implement it. So how does Easy State differ from these?

The technical edge

Historically, transparent reactivity libraries could only work with basic get and set operations. Slightly more complex use cases – like arrays or delete operations – required special handling, which killed the ‘transparent vibe’. Then came Proxies, a meta-programming addition to JavaScript.

Proxies can intercept language operations which were not previously possible. They gave a huge boost to transparent reactivity libraries and both MobX and Vue embraced them since.

Instead of embracing them, Easy State’s core was born out of Proxies 4 years ago, back when they were an experimental API in Chrome only. It is not carrying any bloat from the pre-proxy era and it had a long time to mature during those 4 years. This advantage is noticeable both in the minimalistic API and the stability of the library.

Check out how it holds up against exotic language operations in my Stress Testing React Easy State article.

Practical simplicity

The everyday API consists of two functions only. The rest is automagic and contextual clues to let you focus on business logic instead of reading docs.

Handling global state in React was always a bit clumsy. With Easy State you can create both global and local state with the same API by placing the state accordingly.

Global state

import React from 'react';
import { store, view } from 'react-easy-state';

// this state is defined globally and can be shared between components
const counter = store({
  num: 0,
  increment: () => counter.num++
});

export default view(() => (
  <button onClick={counter.increment}>{counter.num}</button>
));

Local state

import React from 'react';
import { store, view } from 'react-easy-state';

export default view(() => {
  // this state is defined inside the component and it is local to the component
  const counter = store({
    num: 0,
    increment: () => counter.num++
  });

  return (<button onClick={counter.increment}>{counter.num}</button>);
});

So why move under RisingStack?

How does an already stable library benefit from RisingStack’s support? The core is pretty much ‘done’, it didn’t need any commits for the last 13 months. The React port – which is React Easy State – is a different story though. You probably know that React is in the middle of an exciting transition period with hooks and the upcoming async API.

These changes have to be tied together with the core in an intuitive way which is not an easy task. This is where RisingStack is a huge help.

Together we can react quickly to React changes (pun intended).

Join the community today!

Node.js Async Best Practices & Avoiding the Callback Hell

In this post, we cover what tools and techniques you have at your disposal when handling Node.js asynchronous operations: async.jspromises, and async functions.

After reading this article, you’ll know how to use the latest async tools at your disposal provided by Node.js!

Node.js at Scale is a collection of articles focusing on the needs of companies with bigger Node.js installations and advanced Node developers. Chapters:

See all chapters of Node.js at Scale:

Asynchronous programming in Node.js

Previously we have gathered a strong knowledge about asynchronous programming in JavaScript and understood how the Node.js event loop works.

If you have not read these articles, I highly recommend them as introductions!

The Problem with Node.js Async

Node.js itself is single-threaded, but some tasks can run in parallel thanks to its asynchronous nature.

But what does running in parallel mean in practice?

Since we program a single-threaded VM, it is essential that we do not block execution by waiting for I/O, but handle operations concurrently with the help of Node.js’s event-driven APIs.

Let’s take a look at some fundamental patterns, and learn how we can write resource-efficient, non-blocking code, with the built-in solutions of Node.js.

The Classical Approach – Callbacks

Let’s take a look at these simple async operations. They do nothing special, just fire a timer and call a function once the timer finished.

function fastFunction (done) {
  setTimeout(function () {
    done()
  }, 100)
}

function slowFunction (done) {
  setTimeout(function () {
    done()
  }, 300)
}

Seems easy, right?

Our higher-order functions can be executed sequentially or in parallel with the basic “pattern” by nesting callbacks – but using this method can lead to an untameable callback-hell.

function runSequentially (callback) {
  fastFunction((err, data) => {
    if (err) return callback(err)
    console.log(data)   // results of a
  
    slowFunction((err, data) => {
      if (err) return callback(err)
      console.log(data) // results of b
  
      // here you can continue running more tasks
    })
  })
}

Never use the nested callback approach for handling asynchronous Node,js operations!

Avoiding Callback Hell with Control Flow Managers

To become an efficient Node.js developer, you have to avoid the constantly growing indentation level, produce clean and readable code and be able to handle complex flows.

Let me show you some of the tools we can use to organize our code in a nice and maintainable way!

#1: Using Promises

There have been native promises in javascript since 2014, receiving an important boost in performance in Node.js 8. We will make use of them in our functions to make them non-blocking – without the traditional callbacks. The following example will call the modified version of both our previous functions in such a manner:

function fastFunction () {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      console.log('Fast function done')
      resolve()
    }, 100)
  })
}

function slowFunction () {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      console.log('Slow function done')
      resolve()
    }, 300)
  })
}

function asyncRunner () {
    return Promise.all([slowFunction(), fastFunction()])
}

Please note that Promise.all will fail as soon as any of the promises inside it fails.

The previous functions have been modified slightly to return promises. Our new function, asyncRunner, will also return a promise, that will resolve when all the contained functions resolve, and this also means that wherever we call our asyncRunner, we’ll be able to use the .then and .catch methods to deal with the possible outcomes:

asyncRunner()
  .then(([ slowResult, fastResult ]) => {
    console.log('All operations resolved successfully')
  })
  .catch((error) => {
    console.error('There has been an error:', error)
  })

Since node@12.9.0, there is a method called promise.allSettled, that we can use to get the result of all the passed in promises regardless of rejections. Much like Promise.all, this function expects an array of promises, and returns an array of objects that has a status of “fulfilled” or “rejected”, and either the resolved value or the error that occurred.

function failingFunction() {
  return new Promise((resolve, reject) => {
    reject(new Error('This operation will surely fail!'))
  })
}

function asyncMixedRunner () {
    return Promise.allSettled([slowFunction(), failingFunction()])
}

asyncMixedRunner()
    .then(([slowResult, failedResult]) => {
        console.log(slowResult, failedResult)
    })

In previous node versions, where .allSettled is not available, we can implement our own version in just a few lines:

function homebrewAllSettled(promises) {
  return Promise.all(promises.map((promise) => {
    return promise
      .then((value) => {
        return { status: 'fulfilled', value }
      })
      .catch((error) => {
        return { status: 'rejected', error }
      })
  }))
}

Serial task execution

To make sure your tasks run in a specific order – maybe successive functions need the return value of previous ones, or depend on the run of previous functions less directly – which is basically the same as _.flow for functions that return a Promise. As long as it’s missing from everyone’s favorite utility library, you can easily create a chain from an array of your async functions:

function serial(asyncFunctions) {
    return asyncFunctions.reduce(function(functionChain, nextFunction) {
        return functionChain.then(
            (previousResult) => nextFunction(previousResult)
        );
    }, Promise.resolve());
}

serial([parameterValidation, dbQuery, serviceCall ])
   .then((result) => console.log(`Operation result: ${result}`))
   .catch((error) => console.log(`There has been an error: ${error}`))

In case of a failure, this will skip all the remaining promises, and go straight to the error handling branch. You can tweak it some more in case you need the result of all of the promises regardless if they resolved or rejected.

function serial(asyncFunctions) {
    return asyncFunctions.map(function(functionChain, nextFunction) {
        return functionChain
            .then((previousResult) => nextFunction(previousResult))
            .then(result => ({ status: 'fulfilled', result }))
            .catch(error => ({ status: 'rejected', error }));
    }, Promise.resolve());
}

Converting callback functions to promises

Node also provides a handy utility function called “promisify”, that you can use to convert any old function expecting a callback that you just have to use into one that returns a promise. All you need to do is import it in your project:

const promisify = require('util').promisify;
function slowCallbackFunction (done) {
  setTimeout(function () {
    done()
  }, 300)
}
const slowPromise = promisify(slowCallbackFunction);

slowPromise()
  .then(() => {
    console.log('Slow function resolved')
  })
  .catch((error) => {
    console.error('There has been an error:', error)
  })

It’s actually not that hard to implement a promisify function of our own, to learn more about how it works. We can even handle additional arguments that our wrapped functions might need!

function homebrewPromisify(originalFunction, originalArgs = []) {
  return new Promise((resolve, reject) => {
    originalFunction(...originalArgs, (error, result) => {
      if (error) return reject(error)
      return resolve(result)
    })
  })
}

We just wrap the original callback-based function in a promise, and then reject or resolve based on the result of the operation.

Easy as that!

For better support of callback based code – legacy code, ~50% of the npm modules – Node also includes a callbackify function, essentially the opposite of promisify, which takes an async function that returns a promise, and returns a function that expects a callback as its single argument.

const callbackify = require('util').callbackify
const callbackSlow = callbackify(slowFunction)

callbackSlow((error, result) => {
  if (error) return console.log('Callback function received an error')
  return console.log('Callback resolved without errors')
})

#2: Meet Async – aka how to write async code in 2020

We can use another javascript feature since node@7.6 to achieve the same thing: the async and await keywords. They allow you to structure your code in a way that is almost synchronous looking, saving us the .then chaining as well as callbacks:

const promisify = require('util').promisify;

async function asyncRunner () {
    try {
      const slowResult = await promisify(slowFunction)()
      const fastResult = await promisify(fastFunction)()
      console.log('all done')
      return [
        slowResult,
        fastResult
      ]
    } catch (error) {
      console.error(error)
    }
}

This is the same async runner we’ve created before, but it does not require us to wrap our code in .then calls to gain access to the results. For handling errors, we have the option to use try & catch blocks, as presented above, or use the same .catch calls that we’ve seen previously with promises. This is possible because async-await is an abstraction on top of promises – async functions always return a promise, even if you don’t explicitly declare them to do so.

The await keyword can only be used inside functions that have the async tag. This also means that we cannot currently utilize it in the global scope.

Since Node 10, we also have access to the promise.finally method, which allows us to run code regardless of whether the promise resolve or rejected. It can be used to run tasks that we had to call in both the .then and .catch paths previously, saving us some code duplication.

Using all of this in Practice

As we have just learned several tools and tricks to handle async, it is time to do some practice with fundamental control flows to make our code more efficient and clean.

Let’s take an example and write a route handler for our web app, where the request can be resolved after 3 steps: validateParamsdbQuery and serviceCall.

If you’d like to write them without any helper, you’d most probably end up with something like this. Not so nice, right?

// validateParams, dbQuery, serviceCall are higher-order functions
// DONT
function handler (done) {
  validateParams((err) => {
    if (err) return done(err)
    dbQuery((err, dbResults) => {
      if (err) return done(err)
      serviceCall((err, serviceResults) => {
        done(err, { dbResults, serviceResults })
      })
    })
  })
}

Instead of the callback-hell, we can use promises to refactor our code, as we have already learned:

// validateParams, dbQuery, serviceCall are higher-order functions
function handler () {
  return validateParams()
    .then(dbQuery)
    .then(serviceCall)
    .then((result) => {
      console.log(result)
      return result
    })
    .catch(console.log.bind(console))
}

Let’s take it a step further! Rewrite it to use the async and await keywords:

// validateParams, dbQuery, serviceCall are thunks
async function handler () {
  try {
    await validateParams()
    const dbResults = await dbQuery()
    const serviceResults = await serviceCall()
    return { dbResults, serviceResults }
  } catch (error) {
    console.log(error)
  }
}

It feels like a “synchronous” code but still doing async operations one after each other.

Essentially, a new callback is injected into the functions, and this is how async knows when a function is finished.

Takeaway rules for Node.js & Async

Fortunately, Node.js eliminates the complexities of writing thread-safe code. You just have to stick to these rules to keep things smooth:

As a rule of thumb, prefer async, because using a non-blocking approach gives superior performance over the synchronous scenario, and the async – await keywords gives you more flexibility in structuring your code. Luckily, most libraries now have promise based APIs, so compatibility is rarely an issue, and can be solved with util.promisify should the need arise.

If you have any questions or suggestions for the article, please let me know in the comments!

In case you’re looking for help with Node.js consulting or development, feel free to reach out to us! Our team of experienced engineers is ready to speed up your development process, or educate your team on JavaScript, Node, React, Microservices and Kubernetes.

In the next part of the Node.js at Scale series, we take a look at Event Sourcing with Examples.

This article was originally written by Tamas Hodi, and was released on 2017, January 17. The revised second edition was authored by Janos Kubisch and Tamas Hodi and it was released on 2020 February 10.

Generating a Static Site with Hugo + Netlify in 15 minutes

In this article, I’m going to show how you can quickly generate a static site with Hugo and Netlify in an easy way.

What are static site generators, and why do you need one?

Simply put, a static site generator takes your content, applies it to a template, and generates an HTML based static site. It’s excellent for blogs and landing pages.

Benefits:

  • Quick deployment
  • Secure (no dynamic content)
  • Fast load times
  • Simple usage
  • Version control

So, what are the popular options in terms of static site generators?

  • Gatsby (React/JS)
  • Hugo (Go)
  • Next.js (React/JS)
  • Jekyll (Ruby)
  • Gridsome (Vue/JS)

These are the most starred projects on GitHub. I’ve read about Hugo previously, and it seemed fun to try out, so I’m going to stick with Hugo.

What is Hugo?

The official website states that Hugo is the world’s fastest static website engine.

hugo static site generator

We can confirm that it’s really fast. Hugo is written in Golang. It also comes with a rich theming system and aims to make building websites fun again.

Let’s see what we got here.

Installing Hugo

Mac:

brew install hugo

Linux:

sudo apt-get install hugo

or

sudo pacman -Syu hugo

To verify your install:

hugo version

Using Hugo

Create a new project:

hugo new site my-project

Add a theme for a quick start. You can find themes here.

cd my-project
git init
git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke

Add the theme to the config file.

echo 'theme = "ananke"' >> config.toml

Add some content.

hugo new posts/my-first-post.md

It should look something like this:

---
title: "My First Post"
date: 2020-01-05T18:37:11+01:00
draft: true
---

Hello World!

There are lots of options (tags, description, categories, author) you can write to the front matter details.

You can read more about the details here.

Take a look at what we made:

hugo server -D

Open http://localhost:1313

Understanding Hugo’s directory structure

.
├── archetypes
├── assets (not created by default)
├── config.toml
├── content
├── data
├── layouts
├── static
└── themes
  • archetypes: Archetypes are content template files that contain preconfigured front matter (date, title, draft). You can create new archetypes with custom preconfigured front matter fields.
  • assets: Assets folder stores all the files, which are processed by Hugo Pipes. (e.g. CSS/Sass files) This directory is not created by default.
  • config.toml: Hugo uses the config.tomlconfig.yaml, or config.json (if found in the site root) as the default site config file. Instead of a single config file, you can use a config directory as well to separate different environments..
  • content: This is where all the content files live. Top level folders count as content sections. If you have devops and nodejs sections, then you will have content/devops/first-post.md and content/nodejs/second-post.md directories.
  • data: This directory is used to store configuration files that can be used by Hugo when generating your website.
  • layouts: Stores templates in the form of .html files. See the Styling section for more information.
  • static: Stores all the static content: images, CSS, JavaScript, etc. When Hugo builds your site, all assets inside your static directory are copied over as-is.
  • themes: Hugo theme of your choice.

Styling our static site

Remember, we applied a theme before. Now, if we inspect the themes folder, we can see the styling files.

But beware!

DO NOT EDIT THESE FILES DIRECTLY.

Instead, we will mirror the theme directory structure to the root layouts folder.

Let’s say I want to apply custom CSS to the theme.

The theme has a themes/theme-name/layouts/partials folder, where we can find some HTML templates (header.html, footer.html). Now we will edit the header.html template, so copy the content from this file to layouts/partials/header.html and be careful to create the same directory structure like the theme’s into the root layouts folder.

layouts/partials/header.html

themes/theme-name/layouts/partials/header.html

Create a custom CSS file: static/css/custom-style.css.

Add the custom css file to config.toml:

[params]
 custom_css = ["css/custom-style.css"]

Open layouts/partials/header.html:

Add this code inside the <head> tag:

{{ range .Site.Params.custom_css -}}
   <link rel="stylesheet" href="{{ . | absURL }}">
{{- end }}

Now you can overwrite CSS classes applied by your theme.

Deploying our static site to Netlify

One of the benefits of a static site is that you can deploy it easily. Netlify or AWS S3 is a very good choice for hosting a static site. Let’s see how to deploy it to Netlify.

Requirements:

  • Netlify account
  • Github repository

What to do on Netlify

  1. Create a git repository
  2. Create a netlify.toml file into the root of your project with the content below.
[build]
publish = "public"  // default hugo build folder
command = "hugo --gc --minify" // hugo build command

[context.production.environment]
HUGO_VERSION = "0.62.1"
HUGO_ENV = "production"
HUGO_ENABLEGITINFO = "true"
  1. Now, if you push your code to Github, Netlify will deploy the site, and blogging shall start.
  2. Connect Netlify and your Git repository
  3. Build and deploy

You can also take a look at the Hugo official Netlify hosting details for further information.

AWS S3 + CI

We will be using Terraform to create an S3 bucket that will host our static site. I assume you have an AWS account.

Create an s3_bucket.tf file in your project and insert the content below to it:

resource "aws_s3_bucket" "my-static-site" {
  bucket = "my-static-site"
  acl    = "public-read"
  policy = <<POLICY
{
  "Version":"2012-10-17",
  "Statement":[
    {
      "Sid":"PublicRead",
      "Effect":"Allow",
      "Principal": "*",
      "Action":["s3:GetObject"],
      "Resource":["arn:aws:s3:::my-static-site/*"]
    }
  ]
}
POLICY

  website {
    index_document = "index.html"
    error_document = "index.html"
  }
}

Then, folllow these steps:

  • execute terraform init
  • insert the following code snippet
export AWS_ACCESS_KEY_ID="anaccesskey"
export AWS_SECRET_ACCESS_KEY="asecretkey"
export AWS_DEFAULT_REGION="us-east-1"
terraform plan
  • execute terraform apply

This will create a public readable AWS S3 bucket, which will host the static site.

Now we have to take care of building the static site on our own: we need a CI tool.

Setting up CircleCI for continuous integration

At RisingStack, we’re usually using CircleCI for these kinds of tasks. Let’s set up the build process.

Create the following files in the project root: .circleci/config.yml

Apply the following code to config.yml:

version: 2
jobs:
  build:
    docker:
      - image: cibuilds/hugo:latest
    working_directory: ~/hugo
    environment:
      HUGO_BUILD_DIR: ~/hugo/public
    steps:

      # install git
      - run: apk update && apk add git

      # checkout the repository
      - checkout

      # install git submodules for managing third-party dependencies
      - run: git submodule sync && git submodule update --init

      - run:
          name: install AWS CLI (first install pip, the Python package manager)
          command: |
            apk add --update python python-dev py-pip build-base
            pip install awscli

      # build with Hugo
      - run: HUGO_ENV=production hugo -v -d $HUGO_BUILD_DIR
 
      - run:
          name: test our generated HTML files
          command: |
            htmlproofer $HUGO_BUILD_DIR --allow-hash-href --check-html \
            --empty-alt-ignore --disable-external

      # `deploy` step: identical to a `run` step, but uses only one container:
      # /docs/2.0/configuration-reference/#deploy 
      - deploy:
          name: deploy to AWS
          command: |
            if [ "${CIRCLE_BRANCH}" = "master" ]; then
              aws s3 sync $HUGO_BUILD_DIR \
              s3://my-static-site --delete
            else
              echo "Not master branch, dry run only"
            fi

Note: To add AWS credentials, locate the AWS Permissions link under the Permissions section in CircleCI.

Moving forward with Hugo

Hugo offers so much more you should know about. Discover the official documentation here.

Unlimited power

If you’d like to check out the very simple page I created, head over to https://bmz-codez.com/

In case you professional software development services, feel free to reach out to us using our website: https://risingstack.com/nodejs-development-consulting-services

Production Ready with Node.js Training

Hello there, and Happy 2020!

At RisingStack, besides working on consulting and outsourced development projects, we’re actively training engineering teams as well, ranging in size from 5 to 25 ppl at a time.

You might already know that we have 2-days-long training agendas for

These training opportunities are regularly popping up in major European cities, and are also available for companies who’d like to train their engineers on-site, wherever that may be.

In December we prepared a new, 5-days-long training agenda for engineering teams who’d like to dive deep into React and Node, but have little to none experience with these technologies.

Below you can read the full training agenda for the Node training, and you can also check out the 5-days-long React Training Agenda in another blogpost.

In case you have any questions about our training agendas, or if you’d like to invite us, reach out to me at ferenc.hamori@risingstack.com, or use the contact form on our training page.

I’ll get back to you within 24 hrs.

This Node.js training is for developers who:

  • Want to learn how to bootstrap and design scalable software solutions.
  • Want to create and develop production ready Node.js applications.
  • Want to create complex and maintainable web APIs.

The goal of the training is to

  • teach JavaScript and Node.js fundamentals.
  • teach how to bootstrap and design scalable software solutions with Node.
  • teach how to create complex and maintainable web APIs.
  • teach modern application development best-practices & methods.
  • teach relevant production-ready techniques like testing, debugging, logging
    and monitoring with the most efficient tools.

Prerequisites

  • No previous JavaScript knowledge is required – we will cover everything you need to get started.
  • Basic programming knowledge is needed, but any experience with PHP, .NET or Java is sufficient.

Other info

  • This training schedule is delivered throughout 5 days.
  • The training relies heavily on hands-on exercises. During the training, we’re building a social networking site.

Full Node.js Training Agenda

  1. Modern JavaScript Basics
  2. Node.js Basics
  3. Setting Up a Project
  4. The npm module system
  5. API Design
  6. Networking
  7. Database Handling
  8. Queues and Workers
  9. Authentication
  10. Testing
  11. Getting ready for production
  12. Debugging
  13. Performance tuning Node.js applications
  14. Memory profiling Node.js applications

1. Modern JavaScript Basics

The training starts with learning the main concepts of modern JavaScript and ES features.

  • JavaScript runtimes
  • Async programming basics
  • The advantage of promises and async-await over callbacks
  • Understanding functions, context and binding in JavaScript
  • Arrow functions and their relation to this
  • ES6 and beyond

2. Node.js Basics

You will learn the basics of Node.js and when to use it. We will cover async programming patterns, events, streams, and the Node.js process model for concurrent programming. We will also cover the most important Node.js modules.

  • The strange way Node.js handles streams, and why they are important
  • Understanding the most important built-in modules:
    • http
    • fs
    • events
    • child_process
    • cluster

3. Setting Up a Project

We’ll layout the standard project used in Node.js projects, and the role of index.js files discuss the role of environmental variables and how to manage them with dotenv. We’ll also discuss the 12-factor app principles to make sure everyone is on the same page regarding modern web development best practices.

  • 12-factor app: commandments of modern web development
  • Handling configuration with environment variables in Node.js
  • Standard project structure
  • The power and caveats of using index.js files

4. The npm module system

We will cover how to use npm and npm scripts and how to set up a basic project. We’ll also take a look at the npm registry and the module system’s relation to how the require function works, and what to expect from .mjs modules.

  • Understanding npm:
    • cli
    • scripts
    • package.json
  • The difference between common.js and mjs modules
  • The difference between import statements and the require function

5. API Design

You will learn how the main differences between the most used routing libraries: express, koa, fastify, and the built-in http module. Using the most relevant for the group, we’ll also learn how to design an API skeleton, which can be filled with logic later.

  • API design
  • Too many choices, why and how to choose between built-in http, express, koa, and restify
  • Creating an API skeleton with the most relevant package

6. Networking

You will learn how to fetch data from other APIs using the built-in http module and the most used library simply called “request” through the OpenStreetMap project.

  • Using external APIs: our first real-life asynchronous use-case
  • Using built-in http and the industry-standard request package

7. Database Handling

You will learn how to set up and connect to an SQL database or MongoDB. After that, we will design our model abstractions on top of the database and connect them with our API. We’ll also discuss the pitfalls of the most commonly used JavaScript ORM called sequelize, and the features of the most common query builder: knex.

  • Handling data models in Node.js for SQL and / or MongoDB
  • Using the most common modules for SQL: sequelize and knex
  • Using the most common modules for MongoDB: the mongo driver and mongoose

8. Queues and Workers

We will create a worker process and learn how to connect it to the API with messaging queues. After that, we will learn how to create jobs and how to schedule them.

  • Handling long-running tasks in Node.js: worker processes
  • Interfacing with messaging queues
  • The many ways and pitfalls of writing scheduled jobs

9. Authentication

You will learn how to create and manage sessions with Redis and how to use JWT tokens.

  • Session handling: understanding Passport.js
  • Using Redis for session storage
  • Getting rid of external DBs: using JWT tokens in Node.js

10. Testing

We will cover unit testing and mocking with mocha, chai, and sinon and compare it to jest. After that, we will write end-to-end tests.

  • Understanding the two most common test frameworks: mocha and jest
  • Writing proper assertions and expectations with chai and jest
  • Unit testing: the art of mocking with sinon and jest
  • End to end testing
  • Reporters: compliance with enterprise standard tooling

11. Getting ready for production

Writing the business logic consists of the majority of a Node developer’s tasks, but debugging applications that are not prepared properly for production eat up most of their time. To evade these problems, we’ll learn the necessary best practices regarding logging, caching, application lifecycle, and health checking in Node.js apps.

  • Logging with pino and winston
  • Caching in Redis
  • Handling application lifecycle and graceful shutdowns
  • Health checking best practices

12. Debugging

While console.log-s might be enough in many cases, knowing how to use the inspector is a necessary skill for all Node.js developers. We also might need to debug applications in a staging environment, so we’ll learn how to attach debugger sessions to remote processes.

  • The difference between the debugger and inspector
  • Using the inspector locally
  • Debugging on staging: attaching remote sessions

13. Performance tuning Node.js applications

Our job is still not over when the application is out in production. While premature optimization is the root of all evil, measurement-based optimization tuning is necessary in many cases to reduce costs or ensure the stability of our application.

  • CPU profiling and how to read them
  • Common overlooked optimization opportunities
  • Premature optimizations and how to avoid them

14. Memory profiling Node.js applications

Hunting memory leaks can be elusive. Heapdumps tend to be too cryptic to understand easily. To give a better understanding on how to analyze memory usage, we’ll cover some actual case studies. We’ll also learn how to use the current swiss army management of performance and memory usage analyzation: Node clinic.

  • Memory heap dump analysis
  • Swiss army knife of performance and memory analysis: Node clinic
  • Solving real-life memory leak issues from previous projects

Every Article by RisingStack

This page is simply a collection of every article (233) we ever wrote, in reverse chronological order:

2021

2020 (15)

2019 (21 articles)

2018 (32 articles)

2017 (48 articles)

2016 (55 articles)

2015 (40 articles)

2014 (22 articles)

That’s all folks!

Get Hooked on Classless React

Hooks were released this year on February 6, and some of you might have missed them, so this prezentation will get you up to speed.

What is a React Hook?

A Hook is a function provided by React, which lets you hook into React features from your functional component. This is exactly what we need to use functional components instead of classes.
Hooks will give you all React features without classes.
Hooks will make your code more maintainable, they will let you reuse stateful logic, and since you are reusing stateful logic, you will avoid the wrapper hull and the component reimplementation.
Actually, Hooks are backward comfortable, so you can update your React safely.
Hooks make React Great Again. You won’t need MobX or Redux anymore.

So how to use a Hook? Check our examples in the meetup video!
https://www.youtube.com/watch?v=iIQPIlE9npo

Materials

You can check the code I used during my meetup talk:
https://github.com/RisingStack/react-hooks-meetup
You can download my prezentation here:
https://docs.google.com/presentation/d/17cirj5ZfLXk8f4Dh6yKnBVllNGKAy4k3h-PdloVJ14s/edit?usp=sharing

TLDW:

Try the following Hooks in functional React components:

  • useState for local state
  • useContext for global state
  • useEffect for lifecycles
  • useMemo and useCallback for caching
  • create your custom Hooks using built-in Hooks (e.g. useWindowWidth, useDebounce)
  • check other built-in Hooks at React docs

There are two very simple rules if you want to use hooks:

  • Only call Hooks at the top level. Don’t call Hooks inside loops, conditions or nested functions. This will preserve the state of the Hook between multiple states and useEffect.
  • The second rule of Hooks is only call Hooks from react functions. So, you can call Hooks on functional components, or from custom Hooks. This will ensure that all stateful logic in a component is clearly visible for its source code.

Great Content from JSConf Budapest 2019

JSConf Budapest is a JSConf family member 2-day non-profit community conference about JavaScript in the beautiful Budapest, Hungary. RisingStack participated in the conf for several years as well as we did this September.
In 2019 we delivered a workshop called “High-Performance Microservices with GraphQL and Apollo” as our contribution to the event.

budapest-jsconf-2019-risingstack

Now I’m happy to share the news with you that all conference talks are available online!

Top Picks by the Community:

Essential JavaScript debugging tools for the modern detective by Rebecca Hill

Debugging JavaScript can drive developers crazy. It’s not surprising when so many us stick to the trusty console.log – but there are better ways. From tracking down a critical issue in production, to simply struggling to add a new feature and not realising you’ve misread some documentation – debugging skills are used every day but it’s difficult to take the time to improve those skills when the pressure is on.

consoletable-jsconf

This talk will show you some really handy techniques that will level up your skills of deductive reasoning.

Take on me, web browsers! by Eva Ferreira

In 1985 pop music was mesmerized by the a-ha “Take on me” music video. It’s been almost 35 years since then, the world needs new catchy tunes with impressive video animations… on the web.

chroma-jsconf

In this talk we will explore the bewitching ways we can modify web videos and create immersive experiences worthy of the ‘80s using JavaScript and CSS. Let us swim in the why-not possibility of Chroma key, Rotoscoping and more video animation techniques on the web platform!

API Modernization: Building Bridges As You Cross Them by Shelley Vohr

In an ecosystem undergoing constant flux, what does it mean for an API to be modern?In this talk, I’ll discuss the work that’s taken place over the last year to deliver modern JavaScript APIs to developers in the Electron project, and the obstacles we encountered along the way.

api-jsconf


You’ll come away with a deeper understanding of how open source projects can more effectively balance innovation with maintenance, as well as perspectives on how to appropriately consider end-users and their needs when modernization affects the code they use.

Check all JSConf videos below:

Accessibility vs latest Web APIs. Can’t we just get along? by Mauricio Palma

Unfortunately, we still treat accessibility in the same way we deal with front-end development for older browsers, something to be done at the end. What if I tell you that we can use the latest Web APIs and still offer an inclusive and accessible experience.

In this talk, you’ll learn how to combine Web APIs such as Speech Recognition and Geolocation, with performant Javascript techniques to create empathic user interfaces.

Testing in production: Ideas, experiences, limits, roadblocks by Jorge Marin

Are you afraid of testing in production? Do you test in production? Do you use real data?

By definition testing in production is hard. This talk puts together my experience testing in production a large scale system that affects millions of users. Experience, ideas, limits, roadblocks, tips and more.

Weaving the web – Programming textile-based interactions by Charlie Gerard

What if you could interact with interfaces and devices using your clothes? When we think about wearable garments, we usually think of the technology as an output. We might think of LED dresses or designer-made outfits that react to the environment but what if instead, we used this technology as an input, as a way to interact with other things.

This talk walks you through the process of making interactive clothing using conductive textile. Also Charlie shows what it can do and talks about the possibilities and limits of such technology.

Composing music with composed functions by Adam Giese

Functional programming can be difficult to learn. Although there are many practical lessons, they are often hidden through academic lingo and dry examples. What if these basics could be livened up and taught through the lens of music?

Together, we will go over some of the basics of functional programming including functional array manipulation, closure, immutability, and composing functions. Adam also shows how they can be applied to the creation of music and musical instruments using the web audio API.

How not to read the room: Creating socially awkward wearables by Stephanie Nemeth

I’m introvert. This can be bit unfortunate, when you are a person that enjoys spending a lot of their free time creating things bedazzled with LEDs… only to rarely wear them out in public. ¯_(ツ)_/¯ In an effort to actually share my weird and wonderful creations with others, I decided create a wearable project that would force me to be sociable in order for it to reveal its magic.
In this talk, Stephanie shares how she used machine learning with javascript and tiny computers to make “fashion” that is responsive to the people around you and the attention you are (or aren’t) receiving.

Taming Git-osaurus Using Mystical Trees by Damini Satya Kammakomati

Raise your hands if you all start to panic when you mess up your local git workflow, trying frantically to save your work and eventually giving in to the complications thereby deleting your repository. Well, Git isn’t the terrible dinosaur you think it is, on the contrary, the messier it becomes, the more interesting it gets.

This session aims to make friends with Git and to express the hidden gems in the mysterious git land which will definitely help you to become more productive and look cool in front of your peers struggling with a git-gone-wild.

Web Norms of the World: An exploration of the internet beyond the West by Kat Kitay

Flat, muted minimalism, vast fields of whitespace, millennial pink landing pages: the internet today, amirite? In this talk, we’ll take stock of these biases and take a culturally relativistic look at the internet outside of our comfort zone. We’ll explore questions like: Why are Japanese websites so information-dense? How does a script like Arabic, read right to left, affect web design? What languages do (or can) programmers across the world use?

The viewers of this talk will walk away with a fresh perspective and ideas for improving our web and making technology that’s more inclusive of a global audience.

Legendary Lambdas by Tejas Kumar

The Serverless paradigm is one that is slowly taking over the internet. This talk dives deep into Serverless, particularly Serverless Lambda Functions, and their benefits and drawbacks to web applications. We will also discuss how they can benefit business, being extremely cheap to implement and maintain.

As a practical, technical case study, we will examine serverless performance across a number of popular front-end UI frameworks and measure various metrics relevant to a serverless application.

Mastering UIs with Finite State Machines by Rubén Sospedra

Did you ever feel like monkey patching your UI component? Adding too many if/else, handling a lot of complexity or hacking several non-desired side effects. Did you ever have a problem with double-clicking an async button? Fetching multiple times the same resource in a row? Did you have problems translating UX interfaces and mock-ups into your applications scenes?

All this kind of problems can be properly fixed by applying a different point of view. An architecture based upon Mealy state machines. Also known as finite state machines or automatas. These machines are deterministic, pure and idempotents. Opening a new set of possibilities from predictable components to autogenerated tests.

Deciphering Brainwaves with the Web Audio API by Braden Moore

Early last year, my colleagues and I did something amazing — using only JavaScript, the browser, and the Web Audio API, we were able to decipher brainwaves. It sounds sensational, but it’s (mostly) true. This is a story about how we converted brainwaves into audio signals — and then back again — to solve the problem of epilepsy diagnosis on the web.

In this talk, you’ll get to see a new browser API being used in a novel and unprecedented way, combined with world-leading innovations in the field of epilepsy diagnosis. You’ll learn about the challenges of real-time brainwave filtering and how we solved them. As you’ll see, the technologies we use each day can sometimes be applied in unexpected ways.

A privacy first period tracker? Is it even possible? by Benedicte Raae

Do I want to track my cycles? Yes. Do I want the tracker to push my data to a third party? Hell NO! Do I want the data lying around unencrypted in a database somewhere? Not really. Do I want backup and access from multiple devices? Kinda.. What would I need to learn and is it even possible?

Learn how Benedicte created a secure and private web-based period tracker.

StrangerDanger: Finding Security Vulnerabilities Before They Find You! by Liran Tal

Open source modules on the NPM ecosystem are undoubtedly awesome. However, they also represent an undeniable and massive risk. You’re introducing someone else’s code into your system, often with little or no scrutiny. The wrong package can introduce severe vulnerabilities into your application, exposing your application and your user’s data.

This talk will use a sample application, Goof, which uses various vulnerable dependencies, which we will exploit as an attacker would. For each issue, we’ll explain why it happened, show its impact, and – most importantly – see how to avoid or fix it.

Testing presentation components visually by Balázs Korossy-Khayll

You have written all the unit tests, integration and e2e tests imaginable to your project, your code coverage is in the skies, you are sure that everything is in working order, your application is ready to ship. Or is it? Frontend developers often face the challenge that even a plethora of tests don’t cover visual differences, and while the functionality might be working and protected by tests, we don’t know much about the layout’s and visual styles’ correctness.

Writing unit tests or manual testing for visual styles is tiresome and error-prone, so at BlackRock we came up with a better solution. Using Storybook we have developed a way of comparing visual differences of the rendered images of our presentational components.

Update Now! Node.js 8 is Not Supported from 2020.

The Node.js 8.x Maintenance LTS cycle will expire on December 31, 2019 – which means that Node 8 won’t get any more updates, bug fixes or security patches. In this article, we’ll discuss how and why you should move to newer, feature-packed, still supported versions (like Node 12 – check out the new features here).

We’re also going to pinpoint issues you might face during the migration, and potential steps you can take to ensure that everything goes well.

TLDR: The Node.js Release Cycle

If you’d like to bury yourself in the Node Release cycle, you can visit https://github.com/nodejs/Release, or you can read our super-short summary below.

Let’s start with understanding the vital terminology first:

  • Current version: The most recent release line, supported.
  • LTS: A long-term supported version of Node.
    • Active LTS: Actively maintained release, gets new features, bug fixes, improvements.
    • Maintenance LTS: Only critical bug fixes and security fixes.

Once a release moves into Maintenance mode, only critical bugs, critical security fixes, documentation updates, and updates to ensure consistency and usability of the N-API across LTS releases will be permitted. Unless a change is urgent it will be planned into a release once per quarter. Such releases will only be made when necessary. – straight from the Node.js Release Docs.

Node 8’s End of Life is December 31, 2019. This means no more updates.

Take a look at this graph. It might help you to understand how the cycle works:

update-node-js-lts-current-release-schedule-risingstack

New even-numbered versions (e.g. v6, v8, v10, etc) are cut in April, while odd-numbered versions (e.g. v5, v7, v9) are cut in October.

When a new odd-release is cut, the previous even-numbered version transitions to LTS. Every LTS version is actively maintained for 18 months. Then, the LTS version transitions into “maintenance” mode for 12 additional months.

Okay, enough about the release cycle – let’s see how you MUST update Node.js

How to and why update your Node version?

New Node versions come with several bug and security fixes and new features as well. However, the transition from Node 8 to 10 brings a new change. Node not only depends on V8, libuv, and the Node core written in C++, but other projects as well, such as OpenSSL. This time, the maintenance window is aligned with the End Of Life of OpenSSL 1.0.2 and the introduction of OpenSSL 1.1.1 in Node 10+. This brings the possibility to support TLS 1.3 and FIPS.

Node.js versions are mostly backward compatible, meaning that code you wrote for Node 8 will work on Node 10 or 12. Thus, if you only have plain old JavaScript you should face no difficulties upgrading.

Your dependencies may make updating Node a bit more difficult.

Before Node 8, upgrading was a lot more pain, as ES6 feature coverage raised dramatically between Node 6.0 and 8.0. (The majority of the changes came between Node 6.0 and 6.5 but it is safe to assume that most people reading this do not update their Node version that often.)

Adopting these new ES6 features required a lot of code change when we had to update from Node 6 to Node 8. This can be still true with Node 8, if you use native dependencies that rely on the V8 / NAN.

While Node 8 introduced the new N-API as an experimental feature, it has moved to stable with Node 10. The N-API provides an extra abstraction layer over V8 and NAN, making it possible to handle changes to them at a higher level, resulting in a more stable API surface.The problem is that those versions of your dependencies that worked fine with Node 8 might not be compatible with the V8 / NAN version in later @nodejs versions.

Here’s a tale to illustrate this issue:

Let’s say you used a package called awesomeNativeDependecy@1.15.2. It hooked straight into the V8 or NAN API to do its job.

Even while Node 8 was the latest stable version out, the developers of awesomeNativeDependency came out with version 2.0.0 which introduced breaking changes to the functionality of the package.

Some of the native API endpoints changed by the time Node 10 came out, thus the developers of awesomeNativeDependecy decided to only support v2.x.x of their package, so they release awesomeNativeDependecy@2.0.1. This new release is compatible with the native V8 or NAN API changes. Thus, if you wish to update your Node version to 10 or above, you need to use awesomeNativeDependecy@^2.0.1.

This can result in you needing to rewrite the majority of your app’s code where you rely on awesomeNativeDependecy.

This is just one of the many scenarios that can lead to problems when trying to update your Node version past 8.

Even though the surface of Node.js is mostly backward compatible, your dependencies might not work well with older versions, so if you haven’t done so, be prepared for the change.

How not to break your Node app in Production

To make sure you don’t break your production app follow these steps to upgrade your Node version:

  1. Have a code base with good test coverage.
  2. Try to update your Node.js version locally.
  3. Run the tests
  4. Hope all of them pass
  5. If they do, change your Node version on your staging environment
  6. If all went well so far, upgrade your Node version on production

The easiest way to handle changes to your Node version is using nvm.

If you installed Node from source or from a package manager, make sure that when you run which node, you get $HOME/.nvm/versions/node/v$VERSION/bin/node and not in eg. /usr/local/bin/node.

In case it’s not right, make sure that in your $PATH variable $HOME/.nvm/versions/node/v$VERSION/bin/ comes later than wherever which node points to.

This way, you can easily install and switch between different Node versions to go back and forth and see what breaks your tests.

After switching your node versions, make sure you reinstall your dependencies running a clean install with npm run ci. This command will delete your current node_modules folder and install your dependencies completely anew, making sure you’ll get the same error message you would get running npm i on the freshly cloned repo, just as in your CI/CD pipeline.

Start upgrading Node.js 8 now!

If you didn’t update from Node 8 yet, it is high time to get started.According to the current plan, @nodejs 8 will not get critical bug and security fixes starting from 2020 which can leave you exposed to otherwise avoidable threats.

While there is a pretty good chance that in your case it will be hassle free, it is definitely not guaranteed, and if your run into trouble, you might need to rewrite large chunks of your codebase, so better get started sooner than later.

In case you run into any trouble while upgrading your Node services, feel free to reach out to RisingStack at info@risingstack.com, or take a look at our Node.js Support Services.

We’ve been building products with Node.js in the past 5 years, and have the necessary experience to confidently guide you through the process.

React-Native Testing with Expo, Unit Testing with Jest

Welcome back! This is the 6th and final episode of our React-Native tutorial aimed at React developers. In this episode, we’ll make our app a bit more responsive, we’ll do React-Native testing with Expo on both Android and iOS devices. We’ll also improve the developer experience with ESLint for code linting and we’ll learn how to use Jest for React-Native unit testing.

To showcase how you can do these things, we’ll use our Mobile Game which we’ve been building in the previous 5 episodes of this React-Native series.

Quick recap: In the previous episodes of our React-Native Tutorial Series, we built our React-Native game’s core logic, made our game enjoyable with music, sound effects and animations, and even added an option to save our results.

You can check the Github repo of the app here: https://github.com/RisingStack/colorblinder

In the tutorial we’ll be going over the following agenda:

Testing your React-Native App with Expo

Testing Expo apps on a real device

To test your app on a real device while development, you can use the Expo app. First, download it – it’s available both on Google Play and the App Store.

Once you’re finished, run expo start in the project directory, ensure that the development machine and the mobile device are on the same network, and scan the QR code with your device. (Pro tip: on iOS, you can scan QR codes with the Camera app).

Testing Expo apps on an iOS simulator

If you don’t have a Mac, you can skip this section as you cannot simolate iOS without a Mac..

First, install Xcode and start the Simulators app. Then, kick-off by starting multiple simulators with the following screen sizes:

  • iPhone SE (4.0”, 1136×640)
  • iPhone 8 (4.7”, 1334×750)
  • iPhone 8 Plus (5.5”, 1920×1080)
  • iPhone Xs (5.8”, 2436×1125)

(If you are experiencing performance issues, you can test your app in smaller screen size batches, for example, first, you run SE and 8, then when you’re finished, you run the app on 8 Plus and Xs, too).

You can launch the devices needed from the top bar, then launch Expo from the Expo Developer Tools.

Install React Native Expo

You can install the Expo Client on every simulator by repeating the following steps:

  • Closing every simulator you are running
  • Open one simulator that currently does not have the Expo Client installed on it
  • Press i in the Expo packager terminal – it will search for an iOS simulator and install Expo Client on it.
  • Wait for it to install, then close the simulator if you don’t need it anymore

Repeat these steps until you have Expo Client on every simulator installed. Then, you can open the ColorBlinder app itself on every device by typing in the Expo URL of your app into Safari. The Expo URL will look something like exp://192.168.0.129:19000 – you can see yours in the Expo Developer Tools inside the browser, above the QR code.

Testing Expo apps on an Android emulator

If you don’t have an Android device at hand or want to test on a different device type, you’ll need an emulator. If you don’t already have an Android emulator running on your development machine, follow the steps described in the Expo docs to set up the Android Studio, SDK and the emulator.

Please note that even though the Expo docs don’t point this out, to make the adb command work on a Windows device, you’ll need to add the Android SDK build-tools directory to the PATH variable of your user variables. If you don’t know edit the PATH envvar, follow this tutorial. You can confirm that the variable is set up either by running echo %PATH% and checking if the directory is in the string, or running the adb command itself.

Once you have an Android emulator running on your machine, run expo start in the root directory of the project, open the Expo DevTools in your browser and click the “Run on Android device/emulator“ button above the QR code. If everything is set up properly, the Expo app will install on the device and it will load our app.

Making the Sizing a Bit More Responsive

As you could see, the app currently breaks on some screen sizes and does not scale well at all. Lucky for us, React-Native provides us a bunch of tools to make an app look great on every device, like

  • SafeAreaView to respect iPhone X’s notch and bottom bar,
  • the PixelRatio API that can be used to detect a device’s pixel density,
  • or the already used Dimensions API that we used to detect the width and height of the screen.

We could also use percentages instead of pixels – however, ems and other CSS sizing units are not yet available in React-Native.

Optimizing the screens

home screen before optimization react native unit testing jest

The home screen before optimization

game screen before optimization react native unit testing jest

The game screen before optimization

You can see that the texts are using the same size on every device – we should change that. Also, the spacing is odd because we added the spacing to the bottom bars without using the SafeAreaView – thus we added some unneeded spacing to the non-notched devices, too. The grid size also looks odd on the screenshot, but you should not experience anything like this.

First, let’s use the SafeAreaView to fix the spacing on notched and non-notched devices. Import it from “react-native” both in the Home/index.js and Game/index.js, then for the top container, change <View> to <SafeAreaView>. Then in the Home.js, add a <View style={{ flex: 1 }}> before the first and after the last child of the component tree. We can now delete the absolute positioning from the bottomContainer’s stylesheet:

bottomContainer: {
 marginBottom: "5%",
 marginHorizontal: "5%",
 flexDirection: "row"
},

If we reload the app, we’ll see that it looks well, but on iPhone X, the spacing from the bottom is way too big. We could fix that by toggling the bottom margin depending on the device size. I found a really handy utility that determines whether the app runs on an iPhone X[s/r]. Let’s just copy-paste this helper method into our utilities directory, export it in the index.js and import it in the stylesheet of the Home screen:

import { isIphoneX } from "../../utilities";

Then, you can just simply use it with a ternary in the stylesheet:

bottomContainer: {
 marginBottom: isIphoneX() ? 0 : "5%",
 marginHorizontal: "5%",
 flexDirection: "row"
},

The bottom bar will now render correctly on the home screen. Next off, we could continue with making the text size responsible as it plays a crucial role in the app UI and would have a significant effect on how the app looks.

Making the Text Size Responsive

As I mentioned already, we cannot use em – therefore we’ll need some helper functions that will calculate the font sizes based on the screen dimensions.

I found a very handy solution for this from the guys over Soluto (Method 3): it uses the screen’s width and height and scales it from a standard 5” 350×680 size to the display’s current resolution.

Create a file in the utilities, paste the code below into it, export the new utility in the utils/index.js, and import it in every stylesheet and the Header component. After that, wrap the scale() function on every image width/height and fontSize property in your project. For example, there was an image with the properties width: 40, change it to width: scale(40). You can also play around the numbers a little bit if you want to.

import { Dimensions } from "react-native";
const { width, height } = Dimensions.get("window");

//Guideline sizes are based on standard ~5" screen mobile device
const guidelineBaseWidth = 350;
const guidelineBaseHeight = 680;

export const scale = size => (width / guidelineBaseWidth) * size;
export const verticalScale = size => (height / guidelineBaseHeight) * size;

Now, our app looks great on all iPhones – let’s clean up the code!

Cleaning up the Code

Let’s clean up our Game screen a bit because our file is getting very long (it’s 310 lines!): first, extract the grid generator to a separate component.

Create a Grid.js file in the components directory, copy-paste the code below (it’s just the code we already had with some props, nothing new), and export it in the index.js:

import React from "react";
import { View, TouchableOpacity } from "react-native";

export const Grid = ({ size, diffTileIndex, diffTileColor, rgb, onPress }) =>
 Array(size)
   .fill()
   .map((val, columnIndex) => (
     <View style={{ flex: 1, flexDirection: "column" }} key={columnIndex}>
       {Array(size)
         .fill()
         .map((val, rowIndex) => (
           <TouchableOpacity
             key={`${rowIndex}.${columnIndex}`}
             style={{
               flex: 1,
               backgroundColor:
                 rowIndex == diffTileIndex[0] &&
                 columnIndex == diffTileIndex[1]
                   ? diffTileColor
                   : `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,
               margin: 2
             }}
             onPress={() => onPress(rowIndex, columnIndex)}
           />
         ))}
     </View>
   ));

Then, delete the grid from the Game/index.js and add the new Grid component as it follows:

{gameState === "INGAME" ? (
 <Grid
   size={size}
   diffTileIndex={diffTileIndex}
   diffTileColor={diffTileColor}
   rgb={rgb}
   onPress={this.onTilePress}
 />
) : (
...

Next off, we could extract the shake animation because it takes up a bunch of space in our code. Create a new file: utilities/shakeAnimation.js. Copy-paste the code below and export it in the index.js.

import { Animated } from "react-native";

export const shakeAnimation = value =>
 Animated.sequence([
   Animated.timing(value, {
     toValue: 50,
     duration: 100
   }),
   Animated.timing(value, {
     toValue: -50,
     duration: 100
   }),
   Animated.timing(value, {
     toValue: 50,
     duration: 100
   }),
   Animated.timing(value, {
     toValue: -50,
     duration: 100
   }),
   Animated.timing(value, {
     toValue: 0,
     duration: 100
   })
 ]).start();

Then, import it in the Game screen, delete the cut-out code, and use the imported function for starting the animation of the grid. Pass in this.state.shakeAnimation as an argument for our function:

…
} else {
     // wrong tile
     shakeAnimation(this.state.shakeAnimation);
...

Last but not least, we could extract the bottom bar, too. It will require a bit of additional work – we’ll need to extract the styles and a helper function, too! So instead of creating a file, create a directory named “BottomBar” under components, and create an index.js and styles.js file. In the index.js, we’ll have a helper function that returns the bottom icon, and the code that’s been cut out from the Game/index.js:

import React from "react";
import { View, Text, Image, TouchableOpacity } from "react-native";
import styles from "./styles";

const getBottomIcon = gameState =>
 gameState === "INGAME"
   ? require("../../assets/icons/pause.png")
   : gameState === "PAUSED"
   ? require("../../assets/icons/play.png")
   : require("../../assets/icons/replay.png");

export const BottomBar = ({
 points,
 bestPoints,
 timeLeft,
 bestTime,
 onBottomBarPress,
 gameState
}) => (
 <View style={styles.bottomContainer}>
   <View style={styles.bottomSectionContainer}>
     <Text style={styles.counterCount}>{points}</Text>
     <Text style={styles.counterLabel}>points</Text>
     <View style={styles.bestContainer}>
       <Image
         source={require("../../assets/icons/trophy.png")}
         style={styles.bestIcon}
       />
       <Text style={styles.bestLabel}>{bestPoints}</Text>
     </View>
   </View>
   <View style={styles.bottomSectionContainer}>
     <TouchableOpacity
       style={{ alignItems: "center" }}
       onPress={onBottomBarPress}
     >
       <Image source={getBottomIcon(gameState)} style={styles.bottomIcon} />
     </TouchableOpacity>
   </View>
   <View style={styles.bottomSectionContainer}>
     <Text style={styles.counterCount}>{timeLeft}</Text>
     <Text style={styles.counterLabel}>seconds left</Text>
     <View style={styles.bestContainer}>
       <Image
         source={require("../../assets/icons/clock.png")}
         style={styles.bestIcon}
       />
       <Text style={styles.bestLabel}>{bestTime}</Text>
     </View>
   </View>
 </View>
);

And the stylesheet is also just the needed styles cut out from the Game/styles.js:

import { Dimensions, StyleSheet } from "react-native";
import { scale } from "../../utilities";

export default StyleSheet.create({
 bottomContainer: {
   flex: 1,
   width: Dimensions.get("window").width * 0.875,
   flexDirection: "row"
 },
 bottomSectionContainer: {
   flex: 1,
   marginTop: "auto",
   marginBottom: "auto"
 },
 bottomIcon: {
   width: scale(45),
   height: scale(45)
 },
 counterCount: {
   fontFamily: "dogbyte",
   textAlign: "center",
   color: "#eee",
   fontSize: scale(45)
 },
 counterLabel: {
   fontFamily: "dogbyte",
   textAlign: "center",
   color: "#bbb",
   fontSize: scale(20)
 },
 bestContainer: {
   marginTop: 10,
   flexDirection: "row",
   justifyContent: "center"
 },
 bestIcon: {
   width: scale(22),
   height: scale(22),
   marginRight: 5
 },
 bestLabel: {
   fontFamily: "dogbyte",
   color: "#bbb",
   fontSize: scale(22),
   marginTop: 2.5
 }
});

Now, delete any code left in the Game files that have been extracted, export the BottomBar in the components/index.js, import it in the screens/Game/index.js and replace the old code with the component as it follows:

<View style={{ flex: 2 }}>
 <BottomBar
   points={points}
   bestPoints={bestPoints}
   timeLeft={timeLeft}
   bestTime={bestTime}
   onBottomBarPress={this.onBottomBarPress}
   gameState={gameState}
 />
</View>

Now that our code is a bit cleaner and hopefully more understandable for you, we could continue with making our code more readable and consistent by adding ESLint to our project.

Initializing ESLint in React-Native/Expo Projects

If you don’t know already, ESLint is a pluggable linting utility for JavaScript and JSX. You may already have heard of Prettier, but do not mix them, because they both exist for a different reason.

ESLint checks for the logic and syntax of your code (or code quality), while Prettier checks for code stylistics (or formatting). You can integrate Prettier to ESLint too, but adding it to your editor via a plugin will do it for now.

First, install ESLint and some additional tools globally:

npm install --save-dev eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-react eslint-plugin-jsx-a11y babel-eslint

When finished, initialize ESLint with the following command inside your project: eslint --init. Then, select:

  • Use a popular style guide
  • Airbnb
  • Press y if it asks if you use React
  • Pick JSON (if you choose a differing choice, the linter will behave the same way, but we’ll work inside the config file, and you’ll need to work around it a bit to make it work)

Then, restart your editor to make sure that the ESLint server starts in your editor, then open the .eslintrc.json in the root of the project and make sure it contains the following:

{
 "env": {
   "node": true,
   "browser": true,
   "es6": true
 },
 "parser": "babel-eslint",
 "extends": "airbnb"
}

Then, you can play around your code to shut the errors (there will be plenty of them), or simply disable the rules that annoy you. I don’t recommend going to the other extreme by disabling most of the rules since that would make ESLint useless.

You can, however, calmly disable rules like react/jsx-filename-extension that will throw you an error if you DARE to write JSX code inside a .js file, or global-require that will trigger even if you think about using require() inside your code. Don’t get me wrong. I think that they are reasonable rules, but in this project, they are simply not handy.

You can disable ESLint rules in the .eslintrc.json:

"rules": {
  "react/jsx-filename-extension": [0],
  "global-require": [0]
}

For rules,

  • level 0 means disabling a rule,
  • level 1 means setting it to warning level,
  • and level 2 rules will throw an error.

You can read more about configuration in the docs.

Take your time fixing the issues, but before you start throwing out your computer already, be sure to check out the VSCode extension for ESLint.

It comes very handy when introducing ESLint to a previously non-linted project. For example, it can fix automatically fixable problems with just one click – and most of the issues (like spacing or bracket issues) are auto-fixable.

Automated React-Native Unit Testing with Jest

The only thing left before we can mark the project as a finished MVP is adding unit testing. Unit testing is a specialized form of automated testing that runs not only on your machine but in your CI too – so that failing builds don’t get into production.

There are several tools out there like Detox or Mocha, but I chose Jest because it’s ideal for React and React-Native testing. It has a ton of frontend testing features like snapshot testing that Mocha lacks.

If you aren’t familiar with testing yet, I don’t recommend learning it from this article as I will assume that you are already familiar with testing. We already have a very nice article about “Node.js unit testing” – so be sure to check it out to get familiar with some basic ideas and concepts.

Let’s get started with the basics: first, install Jest. With react-native init, you get Jest out of the box, but when using Expo, we need to install it directly. To do so, run yarn add jest-expo --dev or npm i jest-expo --save-dev depending on which package manager you prefer.

Then, let’s add the snippets below to the corresponding places in the package.json:

“scripts”: {
	…
	“test”: “jest”
},
“jest”: {
	“preset”: “jest-expo”
}

Then, install the test renderer library: yarn add react-test-renderer --dev or npm i react-test-renderer --save-dev. That’s it! ?

Now, let’s start by configuring Jest. Jest is a very powerful tool and comes with a handful of options, but for now, we will only add one option, the transformIgnorePatterns. (To learn more about other Jest config options, please head to the docs).

The transformIgnorePatterns option expects “an array of regexp pattern string that are matched against all source file paths before transformation”. We will pass in the following arguments in the package.json:

"jest": {
	"preset": "jest-expo",
	"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|sentry-expo|native-base)"
]
}

This snippet will ensure that every module that we use is transpiled, otherwise Jest might throw syntax errors and make our related tests fail.

Now, that everything’s set up and configured correctly, let’s start by writing our first unit test. I will write a test for the Grid component by creating the file Grid.test.js inside the componentsHome directory, but you can add tests for any file by adding a filename.test.js next to it, and Jest will recognize these files as tests.

Our test will expect to have our Grid to have three children in the tree that gets rendered:

import React from 'react';
import renderer from 'react-test-renderer';

import { Grid } from './Grid';

describe('<Grid />', () => {
 it('has 1 child', () => {
   const tree = renderer
     .create(
       <Grid
         size={3}
         diffTileIndex={[1, 1]}
         diffTileColor="rgb(0, 0, 0)"
         rgb="rgb(10, 10, 10)"
         onPress={() => console.log('successful test!')}
       />,
     )
     .toJSON();
   expect(tree.length).toBe(3); // The length of the tree should be three because we want a 3x3 grid
 });
});

Now, run yarn test or npm test. You will see the test running, and if everything’s set up correctly, it will pass.

Congratulations, you have just created your first unit test in Expo! To learn more about Jest, head over to it’s amazing docs and take your time to read it and play around with it.

What other React-Native Topics Should we Cover?

Thanks for reading my React-Native tutorial series. If you missed the previous episodes, here’s a quick rundown:

I’d like to create more content around React-Native, but I need some help with it! 🙂

It would be great if you could leave a few RN topics in the comment sections which are hard to understand or get-right.

PS: If you need a great team to build your app, reach out to us at RisingStack on our website, or just ping us at info@risingstack.com.

Cheers,
Dani