Scaling / Resizing Responsive Retina CSS Sprite Icon in React

Images are crucial elements that will bring a strong visual power to a web page. Too many images, however, will deteriorate the performance of a web page due to too many requests to the server.

One of the optimisation technique we can use is CSS Sprite, which basically combining multiple images into one image. Then, you could use CSS background-position to shift around and only display part of it.

Pros of CSS Sprite

  • Reduce the requests to server and allow more parallelism request for other resources.
  • Reduce overhead of handshake and header for each HTTP requests.

Cons of CSS Sprite

  • Too large image files may slow down First Meaningful Paint metric and negatively impact other metrics as well. Always benchmark and measure!
  • May have some overhead to regenerate the image sprites every time you add a new image.

Creating CSS Sprite Icon

Today, we are going to try to create an Icon component using CSS sprite in React and see how it works. You can clone the starter project and follow the exercise.

Creating CSS Sprite

There are many ways to generate a CSS sprite:

  • Using cloud tools such as Toptal Sprite Generator and etc.
  • Using image tool such as ImageMagick, Adobe Illustrator and etc.
  • Using Grunt / Gulp / Node tools such as sprity.

For this post, we are not going to look into how to generate the sprite, this process will have integrate into your development flow such as your CI/CD flow. For this tutorial, we already have some generated image sprites that is ready to use in the public/images which contains some icons of social network:

  • icon.webp
  • icon@2x.webp
  • icon@3x.webp.

These icons are generate from image get from flaticon.com.

Let’s get started by adding some classes in App.css:

// Other CSS

+.icon {
+  background: url(/images/icon.webp) no-repeat top left;
+  width: 32px;
+  height: 32px;
+}
+
+@media only screen and (-webkit-min-device-pixel-ratio: 2) {
+  .icon {
+    background: url(/images/icon@2x.webp) no-repeat top left;
+    background-size: 32px 128px;
+  }
+}
+
+@media only screen and (-webkit-min-device-pixel-ratio: 3) {
+  .icon {
+    background: url(/images/icon@3x.webp) no-repeat top left;
+    background-size: 32px 128px;
+  }
+}

We added icon class and set the background to the image with width and height of 32px. Also, to make it display nicely in Retina display devices, we also added 2x and 3x images using -webkit-min-device-pixel-ratio media queries.

Next, we will create an Icon component to display the image. Let’s create a src/Icon.tsx:

import { useMemo } from "react";

export type IconType = 'facebook' | 'instagram' | 'twitter' | 'whatsapp';

export interface IconProps {
  iconName: IconType;
  size?: number;
}

const iconIndex: Record<IconType, number> = {
  facebook: 0,
  instagram: 1,
  twitter: 2,
  whatsapp: 3,
}
const iconSize = 32;

export const Icon = ({ iconName, size = iconSize }: IconProps) => {
  const positionStyle = useMemo(() => {
    return {
      backgroundPosition: `-0px -${iconIndex[iconName] * iconSize}px`,
      transform: `scale(${size / iconSize})`,
    }
  }, [iconName, size]);

  return (
    <div className="icon" style={positionStyle} />
  )
}

By using iconIndex and iconSize, we can easily calculate the position of each image that we want to display. We will pass the iconName as the props to the Icon component and reuse the same component anywhere we want to.

In order to scale the icon, we have added a size props to allow us to change the size of the icon by calculating the scale ratio using the desire size against the actual icon size.

Lastly, let’s add some code to test our Icon component in App.tsx:

import './App.css';
+import { Icon } from './Icon';

function App() {
  return (
    <div className='gallery'>
+      <Icon iconName="facebook" size={16} />
+      <Icon iconName="instagram" size={24} />
+      <Icon iconName="twitter" size={32} />
+      <Icon iconName="whatsapp" size={40} />
    </div>
  );
}

export default App;

Great! We have successfully added 4 Icons in the page with different size and iconName and let’s run yarn start to see the result.

Now we should be able to see the 4 icons is render properly with different sizes.

Conclusion

CSS Sprite is one of the way to optimise your image assets as well as web loading speed.

There are many other image optimisation techniques exist in the industry such as lazy loading the image, using embedded SVG and etc to further allow you to put more images in your webpage without compromise the performance.

Here is the complete code for this tutorial if you interested.

Feel free to leave the comment below.

Create React App v5 is now comes with TailwindCSS v3

Great news people! Create React App v5 is just released with a bunch of upgrades such as support for Webpack 5, PostCSS 8, and one of my favourites is that it comes with TailwindCSS v3.

You are no longer in need of craco or react-app-rewired to get TailwindCSS work with CRA. All you need is just a few simple steps and let me show you.

  1. Create a new react project.
npx create-react-app my-app
  1. Initialize TailwindCSS.
npx tailwindcss init -p
  1. Update tailwindcss.config.js.
module.exports = {
  content: [
++  "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
  1. Import TailwindCSS’s directives in index.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
// Other styles

Voila! Now you can use TailwindCSS in your React app as you like. Feel free to check out the full changelog of Create React App v5. Also worth to mention TailwindCSS new JIT engine is another cool thing to try on too!

Translate Your React app with react-i18next

Localisation is very important for every web application to bring a better user experience to all the users from around the globe and not lose your potential customers.

Today, we will look into how to integrate localisation into your React web app including how to handle language changes, formatting, as well as how to load your localisation file from CDN and etc.

First, let’s start with a simple React app with language selections and a simple text display. You can clone or download the starter project to start with, or if you are impatient, you can get the completed code here.

Setting up react-i18next

Once you have the starter project, install the react-i18next dependency:

yarn add i18next react-i18next

Next, we need to setup react-i18next in our app:

1. Create an i18n.config.js inside the src folder. (Actually, you can name anything you want).

2. Add the following codes into the file.

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

import en from './resources/en.json';
import zh from './resources/zh.json';

i18n
  .use(initReactI18next) // passes i18n down to react-i18next
  .init({
    resources: {
      en: {
        translation: en, // Add translation for English
      },
      zh: {
        translation: zh, // Add translation for Chinese
      },
    },
    fallbackLng: "en",
    interpolation: {
      escapeValue: false // No need to escape for react
    }
  });

3. Import the config in App.js to initialize

// Other import
import i18next from 'i18next';
import './i18n.config';

// Rest of the code

4. Create 2 new files in src/resources/en.json and src/resources/zh.json and insert the content as below.

// src/resources/en.json
{
  "welcome": "Welcome to the world of wonder"
}

// src/resources/zh.json
{
  "welcome": "欢迎来到奇幻的世界"
}

Translate using useTranslation hook

To start translating, you can use useTranslation hook provided by react-i18next. Now let’s add our first translated text inside section tag in App.js:

const App = () => {
++const { t, i18n } = useTranslation();
  // Other code
  const handleLanguageSelect = (event) => {
    setSelectedLanguage(event.target.value);
++  i18n.changeLanguage(event.target.value);
  };
  // Other code

  return (
    <div className="h-screen flex justify-center items-center">
      <div className="mx-auto bg-white p-4 rounded space-y-2">
        // Other code
        <section>
++        <p>{t('welcome')}</p>
        </section>
      </div>
    </div>
  );
};

To change the language, you can always use the changeLanguage method from i18n .

Now, you should be able to see the welcome text is translated based on the language you have selected.

react-i18next translation

Interpolation

Interpolation is a very useful and common feature that we will be used in translation, it allows you to add dynamic values to your translated text.

Let’s say we want to display what language has been selected to the users.

const App = () => {
  // Other code

  return (
    <div className="h-screen flex justify-center items-center">
      <div className="mx-auto bg-white p-4 rounded space-y-2">
        // Other code
        <section>
          <p>{t('welcome')}</p>
++        <p>{t('selectedLanguage', { language: selectedLanguage })}</p>
        </section>
      </div>
    </div>
  );
};

Then add the new translation text in src/resources/en.json and src/resources/zh.json .

// src/resources/en.json
{
  "selectedLanguage": "Your selected language is: {{language}}"
}

// src/resources/zh.json
{
  "selectedLanguage": "您选择的语言是: {{language}}"
}

The differences between the welcome text and the selectedLanguage text is that we pass the language as the second parameter for t method to replace the placeholder in the translation text.

You should see something like this now.

For more details about interpolation, please check the official i18next interpolation documentation.

Formatting

Another powerful feature that i18n provides is you can format the interpolation value. You can either use built-in formatting functions based on Intl API or build your own format function.

To have a better understanding of formatting, we will build a simple uppercase format function. Now, add a simple format method in src/i18n.config.js :

// Other codes
i18n
  .use(initReactI18next)
  .init({
    // Other codes

    interpolation: {
      escapeValue: false,

++    format: (value, format, lng) => {
++      if (format === 'uppercase') {
++        return value.toUpperCase();
++      }
++
++      return value;
      }
    }
  });

The function is really self-explanatory: if the format is uppercase, it will convert the value to uppercase, otherwise, it just returns the value.

Next, we can apply the uppercase format in our selectedLanguage text, let’s update the text in src/resources/en.json and src/resources/zh.json :

// src/resources/en.json
{
--"selectedLanguage": "Your selected language is: {{language}}"
++"selectedLanguage": "Your selected language is: {{language, uppercase}}"
}

// src/resources/zh.json
{
--"selectedLanguage": "您选择的语言是: {{language, uppercase}}",
++"selectedLanguage": "Your selected language is: {{language, uppercase}}"
}
react-i18next translation

If you refresh the page now, you could see the language text has been converted to uppercase.

For more information about formatting, you can refer back to i18next formatting document.

Plural

Another common case that we definitely need when doing the translation is pluralisation, and of course, i18next got it for you too. We will simply count how many times have we changed the language to demonstrate the pluralisation in i18next.

First, let’s add the necessary text in src/resources/en.json and src/resources/zh.json.

// src/resources/en.json 
{
++"numOfTimesSwitchingLanguage": "Your have switch language for {{count}} time",
++"numOfTimesSwitchingLanguage_zero": "Your have switch language for {{count}} time",
++"numOfTimesSwitchingLanguage_other": "Your have switch language for {{count}} times"
}

// src/resources/zh.json 
{
++"numOfTimesSwitchingLanguage": "您已更换了语言{{count}}次",
++"numOfTimesSwitchingLanguage_zero": "您已更换了语言{{count}}次",
++"numOfTimesSwitchingLanguage_plural": "您已更换了语言{{count}}次"
}

Next, let’s add the logic for counting the number of times we change the language in App.js .

const App = () => {
  const { t, i18n } = useTranslation();
  const [selectedLanguage, setSelectedLanguage] = useState('en');
++const [count, setCount] = useState(0);

  const handleLanguageSelect = (event) => {
    setSelectedLanguage(event.target.value);
    i18n.changeLanguage(event.target.value);
++  setCount(count => count + 1);
  };

  return (
    <div className="h-screen flex justify-center items-center">
      <div className="mx-auto bg-white p-4 rounded space-y-2">
        // Other codes
        <section>
          <p>{t('welcome')}</p>
          <p>{t('selectedLanguage', { language: selectedLanguage })}</p>
++        <p>{t('numOfTimesSwitchingLanguage', { count })}</p>
        </section>
      </div>
    </div>
  );
};

Let’s refresh the page and see the outcome.

Great! we can see it changes to plurals once the count is more than 1. One important note here is that we must use count in order for pluralisation to work.

You can do more complex pluralisation with i18next. Please feel free to check out the i18next plural document if you like to.

Persisting and Detecting Language

We have explored a couple of functionality of i18next, but there is one imperfection of our language changes app because it does not persist in the language selection after you refresh the page. Luckily, there is a i18next-browser-languagedetector plugin that going to help us achieve this. This plugin also helps us to detect languages from cookies, URL, browser settings.

Now, let’s install the plugin.

yarn install i18next-browser-languagedetector

Then, add the plugin in src/i18n.config.js:

// Other import
++import LanguageDetector from 'i18next-browser-languagedetector';

i18n
  .use(initReactI18next)
++.use(LanguageDetector)
  .init({
    // Other configs
  });

Previously, we have set en as the default language, but now we want to use i18next.language as the default language. Thus, let’s make some changes in App.js.

const App = () => {
  const { t, i18n } = useTranslation();
--const [selectedLanguage, setSelectedLanguage] = useState('en');
++const [selectedLanguage, setSelectedLanguage] = useState(i18n.language);
  const [count, setCount] = useState(0);
  // Other codes
};

Alright, we can try to switch language to zh and refresh the page now. We should be able to see the default language stays at zh.

Loading Translation Texts From Server

If you’re working with translators or to get better performance with CDN, you may want to load the translation texts from CDN instead of embedding everything in your code.

To do this, i18next-http-backend plugin is here to help us load the translation texts from the server easily.

First of all, let’s install the plugin.

yarn install i18next-http-backend

Then, we need to change some of the configurations in i18n.config.js.

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
--import en from './resources/en.json';
--import zh from './resources/zh.json';
import LanguageDetector from 'i18next-browser-languagedetector';
++import HttpApi from 'i18next-http-backend';

i18n
  .use(initReactI18next)
  .use(LanguageDetector)
++.use(HttpApi)
  .init({
--  resources: {
--    en: {
--     translation: en,
--    },
--    zh: {
--     translation: zh
--    },
--  },
    fallbackLng: 'en',

    interpolation: {
      escapeValue: false,
      /**
       * Add interpolation format method to customize the formatting
       */
      format: (value, format, lng) => {
        if (format === 'uppercase') {
          return value.toUpperCase();
        }

        return value;
      },
    },

++  backend: {
++    loadPath: '/resources/{{lng}}.json',
++  },
  });

Basically, we have made 2 changes: first, we tell the i18n to use the plugin and set the loadPath to /resources/{{lng}}.json where lng will be replaced by the currently selected language code. Secondly, we also remove resources key because it is no longer being used as we are loading the translation texts remotely.

Next, we need to move the src/resources folder to public/resources folder so that the files can be loaded publicly.

Lastly, we need to wrap our application with Suspense in index.js.

import React, { Suspense } from 'react';
// Other codes

ReactDOM.render(
  <React.StrictMode>
++  <Suspense fallback="Loading...">
      <App />
++  </Suspense>
  </React.StrictMode>,
  document.getElementById('root')
);

In practice, we may want to add Suspense around a specific component instead of at the root level. However, for demo purposes, we can just wrap it in the root level.

If you refresh the app, it should work as normal.

Bonus – Namespaces

Speaking of performance, there is another thing that you can do to speed up the loading time of your long translation texts if your application grows large, and that is namespacing. Namespacing allows you to load the small chunk/group of translation texts at a time when you need without preloading everything upfront which is a waste as well as slow down the first load time.

You just need to change your i18next-http-backend loaded path to use the namespaces and add the namespace as the argument to useTranslation .

// i18n.config.js
{
  backend: {
--  loadPath: '/resources/{{lng}}.json',
++  loadPath: '/resources/{{lng}}/{{ns}}.json',
  }
}

// App.js
-- const { t, i18n } = useTranslation();
++ const { t, i18n } = useTranslation('YOUR_NAMESPACE');

To understand more about namespacing, please check out i18next namespaces documentation.

Conclusion

i18next is a robust and sustainable solution to localise your web application. It has many features and plugins to achieve what you need for localisation.

In this post, we are just exploring the tip of the iceberg, there is still much more to learn. I will suggest having a read on react-i18next and i18next documentation to learn how to use them in depth.

I hope you learned something today and please feel free to give any feedback/comment below!

Tailwind CSS v3.0 – New Just-in-Time (JIT) Engine

Tailwind CSS has recently released a new version of 3.0 and it has included many great and powerful new features including new just-in-time (JIT), scroll snap API, new modifier and etc. Today, we will take a quick look at what is the new Just-in-time engine and why it is so important for all Tailwind CSS’s consumers.

What is Tailwind CSS?

If you have never tried Tailwind CSS, it is a collection of opinionated CSS utility classes which allow developers to build the website in a more efficient way. The low-level CSS framework allows you to build a design system with constraints and consistency without wasting or polluting your styles.

What is Just-in-Time?

The classic engine of Tailwind CSS is taking a long time to precompile all CSS classes in advance when developing. Because of the huge CSS classes, it also caused some performance impact in the browser console.

Meanwhile, the new Just-in-time engine allows Tailwind to build the CSS classes on demand and it only generates the CSS classes that are being used. Thus, a smaller CSS file is required as well as much lesser time to compile and preview.

Why Just-in-Time?

Besides lightning-fast build speed and performance boost, the Just-in-Time engine also enables a few exciting features:

All variants are worked out of the box

Due to file size considerations, most of the variants like disabled and active are disabled by default, but with the new JIT engine, where the compilation is on-demand, you can now add any variants without the need of configuring your variants again.

Arbitrary Value Support

If you ever need a custom CSS value that isn’t part of Tailwind CSS, you do not need to write a custom CSS for it. For example, if you want to set the image height to “150px”, you can just add h-[150px] and Tailwind will generate the right CSS class for you. It also works with variants and emojis too, like before:content-['🤪'].

Development styles are the same as production styles

Unlike the purging method used by Tailwind CSS previously, which removes unused styles in production. The new JIT engine is detecting the used classes and generates the styles while you develop. Thus, you do not need to worry about the difference between development and production and avoid any surprises when deploying to production.

How to use Just-in-Time?

If you are using Tailwind 2.1+, you can enable JIT by adding mode: 'jit' in your tailwind.config.js:

// tailwind.config.js
module.exports = {
  mode: 'jit',
  // Other configs
}

If you are using Tailwind 3.x, JIT is enabled by default and you are able to enjoy the benefits from it right away.

Let’s see them in action

First, let’s test the compilation speed of the JIT engine.

TailwindCSS Performance

We can see the initial compilation speed took around 185ms and the subsequent compilation is only took around ~20ms, which is very fast.

Next, we want to add ✅ symbol before the title to make it less boring. Without the JIT engine, we may need to create a custom CSS class to achieve this. However, JIT allows us to add custom styles without needing custom CSS classes. The idea is to place the value you need in a square bracket. Here, we can add before:content-['✅'] in the title’s class.

TailwindCSS Arbitrary Value

One note is that you must not have any spaces in arbitrary value, for example, if you want to create a custom grid column like grid-template-columns: auto, minmax(0, 1fr), auto, minmax(0, 1fr), you need to write it without any spaces, like grid-cols-[auto,minmax(0,1fr),auto,minmax(0,1fr)]. Otherwise, Tailwind will not able to compile the class properly for you.

Now, let’s try to add another variant, lets say, we want to change the text selection background color to red color. What we can do is directly add selection:bg-red-400. and do not need to worry if the variant has been configured.

Tailwind CSS JIT supports all variants

Conclusion

In this post, we have roughly gone through how Tailwind CSS JIT works and what benefits it can bring to us. In my opinion, JIT is definitely a great enhancement for Tailwind and you should enable it in your project whenever possible because it will speed up and improve your development experience.

Testing with React-Redux Hooks

With the rise of React Hooks, many developers have added hooks into their open source libraries. If you are using react-redux libraries, they also provided hooks such as useSelector and useDispatch as well.

Although hooks allow us to use redux with ease in React, however, there is not much documentation about unit testing with hooks. Thus, I’m going to show you how to unit test your react-redux hooks.

In class component, we can create a mock of the redux actions and states and pass them into the component for unit testing. As a matter of fact, there is not much difference in unit testing functional components with hooks. Instead of passing the mock state into the components, we can mock the implementation of `useSelector` to return state.

Imagine we have a component that looks like this:

import React from 'react';
import { useSelector } from 'react-redux';

const HelloWorld = () => {
  const user = useSelector(state => state.user);
  const skills = useSelector(state => state.skills);

  const { firstName } = user;

  return (
    <div>
      <p>First Name: {firstName}</p>
      <p>My Skills:</p>
      <ul>
        {skills.map(skill => (
          <p key={skill.id}>{skill.name}</p>
        ))}
      </ul>
    </div>
  );
}

export default HelloWorld;

We know useSelector is a function that takes a callback as the parameter. All we need to do is to mock the state and pass to the callback. The unit test would look like this:

import React from 'react';
import { render } from '@testing-library/react';
import { useSelector } from 'react-redux';
import HelloWorld from './HelloWorld';

jest.mock('react-redux', () => ({
  useDispatch: jest.fn(),
  useSelector: jest.fn(),
}));

describe('HelloWorld', () => {
  test('renders user with skills', () => {
    useSelector.mockImplementation((selector) => selector({
      user: {
        firstName: 'Tek Min',
      },
      skills: [
        {
          id: '1',
          name: 'Javascript',
        },
        {
          id: '2',
          name: 'React',
        },
      ],
    }));
    const { container } = render(<HelloWorld />);
    expect(container).toMatchSnapshot();
  });

  test('renders user without skills', () => {
    useSelector.mockImplementation((selector) => selector({
      user: {
        firstName: 'Tek Min',
      },
      skills: [],
    }));

    const { container } = render(<HelloWorld />);
    expect(container).toMatchSnapshot();
  });
});

If you check the snapshot, you will probably see something like this:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`HelloWorld renders user with skills 1`] = `
<div>
  <div>
    <p>
      First Name: 
      Tek Min
    </p>
    <p>
      My Skills:
    </p>
    <ul>
      <p>
        Javascript
      </p>
      <p>
        React
      </p>
    </ul>
  </div>
</div>
`;

exports[`HelloWorld renders user without skills 1`] = `
<div>
  <div>
    <p>
      First Name: 
      Tek Min
    </p>
    <p>
      My Skills:
    </p>
    <ul />
  </div>
</div>
`;

Great! we get the snapshot as we wanted, the components render correctly with the different redux states.

You may find another way of testing with redux hooks in Google, but I find that this is one of the simplest versions of doing it without additional libraries. Hope you find it helpful and have a nice day!

Monitor Apollo GraphQL with Graph Manager (Part 1)

GraphQL has been rapidly gaining the popularity due to its flexibility of giving the clients to ask what they need and the power of evolving the APIs with ease. There are 2 major libraries that allow us to integrate GraphQL into your web and mobile applications, which are Relay and Apollo. These libraries provide a lot of functionality such as security, caching, subscription and etc for the client to use.

However, most of the time, we may want to understand how are the clients use our GraphQL API and take immediate actions to solve the issue for particular client. Apollo GraphQL gives us a good feature called client-awareness that allow us to monitor on the usage of the Apollo GraphQL API with Graph Manager.

Today, we are going to take a look on how to use client-awareness feature in Apollo GraphQL.

Adding name and version to Apollo Client

Apollo provides different client libraries such as apollo-react for React or React Native apps, apollo-ios for native iOS and apollo-android for native Android. First, to leverage the client awareness feature, we need to add name and version to the config of ApolloClient.

If you are using apollo-react:

import ApolloServer from 'apollo-boost';

const client = new ApolloClient({
  name: 'MyReactApp', // Add this
  version: '1.0.0.1000', // Add this
});

If you are using apollo-ios:

import Foundation
import Apollo

class Network {
  static let shared = Network()
  
  private lazy var networkTransport = HTTPNetworkTransport(
    url: URL(string: "https://www.mygraphapi.com")!
  )
    
  private(set) lazy var apollo = ApolloClient(networkTransport: self.networkTransport)
    
  init() {
    self.networkTransport.clientName = "MyiOSApp";
    self.networkTransport.clientVersion = "1.0.0.100";
  }
}

If you are using apollo-android:

Unfortunately, apollo-android does not support this feature yet. However, we can set the header by ourselves.

var okHttpClient = OkHttpClient
  .Builder()
  .addInterceptor { chain ->
    val original = chain.request()
    val builder = original.newBuilder().method(original.method(), original.body())
    builder.header("apollographql-client-name", "MyAndroidApp")
    builder.header("apollographql-client-version", "1.0.0.100")
    chain.proceed(builder.build())
  }
  .build()

var apolloClient = ApolloClient.builder()
  .serverUrl(BASE_URL)
  .okHttpClient(okHttpClient)
  .build()

You can inspect the http request from iOS simulator and Android emulator with Charles Proxy (Or Reactotron for React Native). You will notice 2 headers: apollographql-client-name and apollographql-client-version have been added to the request headers.

Accessing name and version in Apollo Server

Now, we can access the headers in the Apollo Server like this:

import  ApolloServer from 'apollo-server';

const server = new ApolloServer({
  ...
  context: ({ req }) => {
    const { headers } = req;
    return {
      clientName: headers['apollographql-client-name'],
      clientVersion: headers['apollographql-client-version'],
    };
  },
});

If you have enabled CORS in Apollo Server, remember to whitelist these headers.

We are all set for client awareness integration. In the next tutorial, we will talk about how to enable Graph Manager to monitor your Apollo GraphQL API. Stay tuned!

Using Custom Fonts in React Native

If you are looking for the guide to use custom fonts in your React Native apps, this is the right place for you.

Adding custom fonts to your React Native apps is not a difficult task, but there are few tweaks that you need to do to make sure the custom fonts are set up correctly. I will share with you steps by steps in details to set up the custom fonts.

Prepare the fonts

First, we need to get the fonts ready. For this tutorial, we will use DancingScript font as the custom font. If you wish to use other font, you may download them from Google Fonts.

There is one thing you may need to take note, Android will use the font’s file name as the font family, while iOS will use “PostScript name” as the font family. Let’s take a look of the example below:

Font files
Actual font files

If we use these fonts as custom fonts, we can use fontFamily: "DancingScriptRegular" in Android to show the custom font. However, this may not work in iOS if the “PostScript name” is different than the font’s file name. To check the “PostScript name” of the font, you can add the fonts to Font Book and view the details.

Dancing Script details from Font Book
Dancing Script details from Font Book

As the PostScript name for the font is “DancingScript-Regular” instead of “DancingScriptRegular”, setting fontFamily: "DancingScriptRegular" in the style will not show the custom font in iOS.

Thus, it is always recommend to rename the font file to the “PostScript name” so that we can use the same fontFamily for both iOS and Android platform. So, let us rename our fonts.

Add fonts to assets

Next, we need to add the fonts to the assets. Create the directory assets/fonts and put all the fonts into the directory.

If you are using React Native >= 0.60

Since rnpm is deprecated, we will use the new method to link the assets. Create a file react-native.config.js and put the following codes:

module.exports = {
  project: {
    ios: {},
    android: {},
  },
  assets: ['./assets/fonts/'],
};

If you are using React Native < 0.60

You may add the following codes into your package.json:

"rnpm": {
  "assets": [
    "./assets/fonts/"
  ]
}

Link the font assets

Once the fonts is in the right place, we can link them by running the follow command:

react-native link

To verify if your fonts is correctly linked, you can check if the fonts is copied to android/app/src/main/assets/fonts and added to Build Phases > Copy Bundle Resources of your iOS project.

Use the custom fonts in styles

Now you can use the font in your React Native app, just add fontFamily: YOUR_FONT_NAME to your Text or TextInput component and it will show the text and placeholder in custom font. I highly recommend always create a shared custom Text and TextInput in your React Native app so that you can apply the style to all the text and input in your app directly.

Bonus: Unlinking the fonts

Unfortunately, there is not command that can the fonts automatically, you will have to do it manually.

  1. Remove the fonts in assets/fonts.
  2. Remove the fonts in android/app/src/main/assets/fonts.
  3. Remove reference of fonts in Resources of your iOS project. (This will remove the reference in the Build Phases > Copy Bundle Resources too.)

Thank you for reading, I hope you find this tutorial useful!

React Native Searchable FlatList with React Hooks

If you have been using a mobile app or building one, you must be very familiar with the content of a very long list such as merchandise products, contacts, friends, countries, etc. It is very inconvenient for the user to find the required record by scrolling the whole list. Most of the mobile apps will provide a search bar for the user to search for their desire record for better user experience.

In React Native, FlatList is the common component that you will use to build the long list. FlatList can render the long and performant list just with 2 basic props: renderItem and data. If you wish to use sections, you may use <a rel="noreferrer noopener" aria-label="SectionList (opens in a new tab)" href="https://facebook.github.io/react-native/docs/sectionlist&quot; target="_blank">SectionList</a>.

For this tutorial, we will build a basic searchable country list similar to the GIF below and the solution will be demonstrated using React Hooks.

Country Selection List from Spendie Mobile App
Country Selection List in Spendie (iOS & Android)

Without any further ado, let’s get started!

Building The List

First, let’s build the UI of a search bar and a simple list of countries.

import React, { useState } from 'react';
import { View, Text, FlatList } from 'react-native';
import { SearchBar } from 'react-native-elements';
import { countries } from 'countries-list';

// Get the values of the countries and sort is alphabetically
const countryList = Object.values(countries)
  .map((country) => ({
    ...country,
    lowerCaseName: country.name.toLowerCase(),
  }))
  .sort((a, b) => a.name > b.name);

const SearchableFlatList = () => {
  const [query, setQuery] = useState('');
  const [filteredCountryList, setFilteredCountryList] = useState(countryList);

  return (
    <View>
      <SearchBar
        placeholder="Search your countries..."
        onChangeText={setQuery}
        value={query}
      />
      <FlatList
        keyExtractor={(item, index) => `${index}`}
        data={filteredCountryList}
        renderItem={({ item }) => <Text>{item.name}</Text>}
      />
    </View>
  );
};

export default SearchableFlatList;

From the code snippet above, we are using open source <a rel="noreferrer noopener" aria-label="countries-list (opens in a new tab)" href="https://github.com/annexare/Countries&quot; target="_blank">countries-list</a> to get the list of countries. Due to countries is a key-value object, thus, we need to use Object.values to get the array of the country. You must be curious why we need to add lowerCaseName for each country. The reason is that we may want to ignore the cases when we search the list. Thus, by adding the lowerCaseName, we can use it to make our search case-insensitive. Last but not least, we also sort them alphabetically according to the name.

Then, we create a state filteredCountries for holding the filtered country list and set the initial value to the countryList. Next, assign the filteredCountries to the FlatList for rendering.

For the search bar, we can use awesome SearchBar component from <a rel="noreferrer noopener" aria-label="react-native-elements (opens in a new tab)" href="https://react-native-elements.github.io/react-native-elements/docs/getting_started.html&quot; target="_blank">react-native-elements</a>. After that, we create another state query and set it when user changes the input of the search bar.

Until now, we have a search bar and a list of countries display on the screen. However, there is still no logic to search the country list when the search bar’s value is changed. Let’s add the search logic now.

Search the List

We have all the UI set and we can add the search logic now.

useEffect(() => {
  const lowerCaseQuery = query.toLowerCase();
  const newCountries = countryList
    .filter((country) => country.lowerCaseName.includes(lowerCaseQuery));

  setFilteredCountryList(newCountries);
}, [query]);

Now, we will use the effect hook and put query as the dependency of the hook. By setting the dependency, the effect hook will only be called when the query is changed. Similar to what we did previously, we will create a lower case version of the query so that we can compare with the lower case version of the country’s name. We use includes to filter the country that contains the query in its name. Don’t forget to import useEffect in your file!

Now, you can run your React Native app and you should able to search the country list when you type in the search bar.

Sorting the Result

Sometimes, for better user experience, you may want to sort the result based on the search queries. For example, if user type o in the search box, you want to display the country’s name that start with o at the top of the result. To achieve this, we can rank our result based on the indexOf the query in the country’s name and sort them accordingly.

useEffect(() => {
  const lowerCaseQuery = query.toLowerCase();
  const newCountries = countryList
    .filter((country) => country.lowerCaseName.includes(lowerCaseQuery))
    .map((country) => ({
      ...country,
      rank: country.lowerCaseName.indexOf(lowerCaseQuery),
    }))
    .sort((a, b) => a.rank - b.rank);

  setFilteredCountryList(newCountries);
}, [query]);

Using Debounce

Besides ranking the result, there is another enhancement we can do for the search, It is not wise if we try to search the result while the user is still typing because you may need to do a lot of processing and may create flickering on the screen. If you are calling API for the filter/search, you will have a lot of redundant calls and handling for race condition. To fix the problem easily, we can use debounce to wait until the typing action is finished before we proceed to search the result. It will create a more performant and responsive experience for the user. To understand more about debounce, you may read this tutorial.

Let’s create a simple debounce hook.

const useDebounce = (value: any, delay: number) => {
  const [debounceValue, setDebounceValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebounceValue(value);
    }, delay);

    return () => {
      clearTimeout(timer);
    };
  }, [value, delay]);

  return debounceValue;
};

Next, we will debounce the query for 300 milliseconds. It will gives the user a 300 milliseconds window to type the next character before we start to search the result.

const debounceQuery = useDebounce(query, 300);

Lastly, we change the effect hook’s dependencies from query to debounceQuery.

useEffect(() => {
  const lowerCaseQuery = debounceQuery.toLowerCase();
  ...
}, [debounceQuery]);

By adding the debounce, we can save some processing power when user is typing in the search bar. This is the final code snippet for this tutorial.

import React, { useState, useEffect } from 'react';
import { View, Text, FlatList } from 'react-native';
import { SearchBar } from 'react-native-elements';
import { countries } from 'countries-list';

const useDebounce = (value: any, delay: number) => {
  const [debounceValue, setDebounceValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebounceValue(value);
    }, delay);

    return () => {
      clearTimeout(timer);
    };
  }, [value, delay]);

  return debounceValue;
};

// Get the values of the countries and sort is alphabetically
const countryList = Object.values(countries)
  .map(country => ({
    ...country,
    lowerCaseName: country.name.toLowerCase(),
  }))
  .sort((a, b) => a.name > b.name);

const SearchableFlatList = () => {
  const [query, setQuery] = useState('');
  const debounceQuery = useDebounce(query, 300);
  const [filteredCountryList, setFilteredCountryList] = useState(countryList);

  useEffect(() => {
    const lowerCaseQuery = debounceQuery.toLowerCase();
    const newCountries = countryList
      .filter((country) => country.lowerCaseName.includes(lowerCaseQuery))
      .map((country) => ({
        ...country,
        rank: country.lowerCaseName.indexOf(lowerCaseQuery),
      }))
      .sort((a, b) => a.rank - b.rank);

    setFilteredCountryList(newCountries);
  }, [debounceQuery]);

  return (
    <View>
      <SearchBar
        placeholder="Search your countries..."
        onChangeText={setQuery}
        value={query}
      />
      <FlatList
        keyExtractor={(item, index) => `${index}`}
        data={filteredCountryList}
        renderItem={({item}) => <Text>{item.name}</Text>}
      />
    </View>
  );
};

export default SearchableFlatList;

Congratulations! We successfully build a searchable FlatList in React Native.

Hope you like the tutorial and able to build a performant search in React Native. I believe there are many ways to achieve this. If you found any better solution, please feel free to share it in the comment below!

Supercharge React Native Android app with Hermes

Facebook officially released the Hermes engine as an opt-in feature on Android. By enabling Hermes, Facebook claims that it will improve startup time, decrease memory usage and reduce app size. Let’s find out!

Prerequisite

To enable Hermes, your app needs to be at least using React Native 0.60.4 and above. If you are using the earlier version of React Native, you may need to upgrade it with React Native Upgrade Helper.

Enable Hermes

Just a few simple steps that are required to enable Hermes in React Native.

Find the line of enableHermes: false in your app/build.gradle and change it to true.

project.ext.react = [
     entryFile: "index.js",
     enableHermes: true  // clean and rebuild if changing
]

If you are using proguard, add the following line in your proguard-rules.pro

-keep class com.facebook.hermes.unicode.** { *; }

You need to clean your project if you have built your app before.

$ cd android && ./gradlew clean
$ react-native run-android

Voila! Your Android app is now using Hermes engine.

Confirming Hermes is in Use

To verify if Hermes has been enabled, you can always verify it with the following code.

const isHermes = () => global.HermesInternal != null;

Analysing the result

Now, let’s take a look at how much improvement that Hermes can do. I have enabled Hermes in one of my React Native app and let’s look at the result

Spendie – An Expenses Tracking App

Before HermesAfter HermesImprovement
App Size (APK)26.4MB16.8MB36% size reduced
Spendie (with Hermes enabled) vs Spendie (without Hermes enabled)

Common Issue about Hermes

#1 Build error in CI/CD environment

If you are using React Native 0.60.x, you may hit compilation error in your CI/CD environment such as TravisCI, CircleCI or even Github Actions.

Process 'command '../../node_modules/hermesvm/linux64-bin/hermes'' finished with non-zero exit value 135

This issue should be fixed in latest release of React Native 0.61.x. However, if you are not able to upgrade React Native version. You may apply the following fixes.

First, you need to install hermes-engine.

yarn add -D hermes-engine

Update the hermesCommand to use the hermes-engine in your app/build.gradle

project.ext.react = [
    entryFile: "index.js",
    enableHermes: true,
    hermesCommand: "../../node_modules/hermes-engine/%OS-BIN%/hermes", // Add this line
]

Change the hermesvm to hermes-engine in app/build.gradle

// Find this line
def hermesPath = "../../node_modules/hermesvm/android/";

// Change to the following
def hermesPath = "../../node_modules/hermes-engine/android/";
#2 Blank screen when upload to Play Store with bundle

Many issues have been reported that if you bundle your app and upload to Play Store, your app will show blank screen when start. To temporary fix this problem, you may use assemble instead of bundle at the moment.

Although you may faced some problem with Hermes engine but please feel free to enable Hermes for your React Native app and enjoy the performance boost!

To understand more about Hermes engine, you may read the Facebook post.