Today I Learned

This project exists to catalogue the sharing & accumulation of knowledge as it happens day-to-day. Posts have a 200-word limit, and posting is open to any Rocketeer as well as selected friends of JetRockets. We hope you enjoy learning along with us.

12 posts about #javascript

JS: Parsing URL

So far, I have used the indexOf operator or regular expressions to get query string values. Today I learned how to make it easier using the URLSearchParams API:


// Assuming "?post=1234&action=edit"

let urlParams = new URLSearchParams(window.location.search);

console.log(urlParams.has('post')); // true
console.log(urlParams.get('action')); // "edit"
console.log(urlParams.getAll('action')); // ["edit"]
console.log(urlParams.toString()); // "?post=1234&action=edit"
console.log(urlParams.append('active', '1')); // "?post=1234&action=edit&active=1"

Important: Does not work in IE

Typeahead async requests throttling

As everybody else have some legacy code (who does not have it?), and yesterday we faced with problem.

Our internal app have search panel where users could write and get related results. But search was written with typeahead and without any throttling. So it’s triggered full text search on backend side on every input change which leads to DB connection pool exhaustion.

We didn’t found any ready to use solutions, so we added following fix:

$input.typeahead(
  {
    ...
  },
  {
        ...
    async: true
    source: (query, syncResults, asyncResults) ->
      href = self.href
      # Added getAsyncResults call to throttle requests:
      getAsyncResults(
        () ->
          $.ajax(
            url: href
            data:
              query: query
            dataType: 'json'
          ).success(
            (data, textStatus, jqXHR) ->
              asyncResults(data)
          )
      )
  })

where getAsyncResults defined before typeahead init:

# throttle is lodash function
getAsyncResults = _.throttle(((fn) ->
    fn()
  ), 300,
  { ... })

Would be happy if someone find this simple solution helpfull.

Trick: filter falsy values out of an array?

Lets say you need to filter all falsy

0, undefined, null, false, ''(empty string), NaN

values from array.

Of course you can use following construnction:


myArray
    .map(item => {
        // ...
    })
    // Get rid of falsy values
    .filter(item => item);

Since the values we don’t want aren’t truthy, the filter above removes those falsy items.

Did you know there’s a clearer way with Boolean?


myArray
    .map(item => {
        // ...
    })
    // Get rid of falsy values
    .filter(Boolean);

Integrate Drawer inside Stack Navigation in React Native

I wanted to use more than one navigation in a React Native app. The documentation didn’t give a clear way to implement this.

Here is what you can do on purpose to add several types of navigation in your React Native app:


import { createStackNavigator, createDrawerNavigator } from 'react-navigation';
import * as Screens from './screens/index';
import Drawer from './components/DrawerMenu';
import getDrawerWidth from './utils/scale';

const AppStackNavigator = createStackNavigator({
  home: {
    screen: Screens.MainScreen,
  },
  about: {
    screen: Screens.AboutScreen,
  },
});

const AppNavigator = createDrawerNavigator(
  {
    home: {
       screen: AppStackNavigator,
    },
  },
  {
    contentComponent: Drawer,
    drawerWidth: getDrawerWidth,
    headerMode: 'none',
  }
);

export default AppNavigator;

Get most performant function

Let’s say you need to determine which function is execute faster.

Write a function that take array of functions and iterates each of them a certain number of times. Will use the difference in performance.now() values before and after to get the total time in milliseconds each iteration running.

Play with iterations argument to get more or less reliable results.

const mostPerformantFunction = (fns, iterations = 10000) => {
  const times = fns.map(fn => {
    const before = performance.now();
    for (let i = 0; i < iterations; i++) fn();
    return performance.now() - before;
  });
  console.log(times);
  return times.indexOf(Math.min(...times));
};
mostPerformantFunction([
  () => {
    [1, 2, 3, 4, 5, 6, 7, 8, 9, '10'].every(el => typeof el === 'number');
  },
  () => {
    [1, '2', 3, 4, 5, 6, 7, 8, 9, 10].every(el => typeof el === 'number');
  }
]); // 1

How to download files with Axios

A simple example using the Blob() constructor:

export function someFunction(values) {
  return (dispatch) => {
    ...
    const method = 'GET';
    const url = 'http://go.api/download_file';
    ...
    axios
      .request({
        url,
        method,
        responseType: 'blob', //important
      })
      .then(({ data }) => {
        const downloadUrl = window.URL.createObjectURL(new Blob([data]));
        const link = document.createElement('a');
        link.href = downloadUrl;
        link.setAttribute('download', 'file.zip'); //any other extension
        document.body.appendChild(link);
        link.click();
        link.remove();
      });
  };
}

Recompose withContext and getContext. Simple example

Very often for nested components you need to transfer some props of the parent. For example size. So, we have the Card component. It contains a Button component.

  // App.js
  <Card size="sm">
    {/* ... */}
    <div>
      {/* ... */}
      <Button size="sm" onClick={action}>Action</Button>
    </div>
  </Card>

The size for both components is set by the “size” parameter. In the example above, the parameter passed is indicated for both components. In order not to specify the size for the Button each time, you can use the Context to use the one specified for the Card. If you use recompose, it can be super easy:

  // Card.js
  import { getContext } from 'recompose';
  // ...
  export default withContext(
    { size: PropTypes.oneOf(['sm', 'md', 'lg']) },
    ({ size }) => ({ size }),
  )(Card);
  
  // App.js
  import Button from './Button';
  // ...
  const EnhancedButton = getContext({ size: PropTypes.oneOf(['sm', 'md', 'lg']) })(Button);

Live example

How to display numbers with currency formatting in JS?

Use Intl.NumberFormat to enable country / currency sensitive formatting.

const toCurrency = (n, curr, LanguageFormat = undefined) =>
  Intl.NumberFormat(LanguageFormat, { style: 'currency', currency: curr }).format(n);
toCurrency(123456.789, 'EUR'); // €123,456.79  | currency: Euro | currencyLangFormat: Local
toCurrency(123456.789, 'RUB'); // RUB 123,456.79  | currency: Ruble | currencyLangFormat: Local
toCurrency(123456.789, 'RUB', 'Ru-ru') // 123 456,79 ₽  | currency: Ruble | currencyLangFormat: Russian
toCurrency(123456.789, 'USD', 'en-us'); // $123,456.79  | currency: US Dollar | currencyLangFormat: English (United States)
toCurrency(123456.789, 'USD', 'fa'); // ۱۲۳٬۴۵۶٫۷۹ ؜$ | currency: US Dollar | currencyLangFormat: Farsi
toCurrency(322342436423.2435, 'JPY'); // ¥322,342,436,423 | currency: Japanese Yen | currencyLangFormat: Local
toCurrency(322342436423.2435, 'JPY', 'fi'); // 322 342 436 423 ¥ | currency: Japanese Yen | currencyLangFormat: Finnish

Passing events between browser tabs without WebSockets

What if need to pass some event from one browser tab to each others? And we don’t have WebSockets…

It turns out that localStorage raises events 😍

Namely, events are triggered when an item is added, deleted, or changed in another context. In essence, this means that when you change localStorage in one tab, other tabs can find out about this by listening to the storage event of the global window object. For example:

  window.addEventListener('storage', function (event) {
    console.log(event.key, event.newValue);
  });

Of course, there are some restrictions on use (you can read about them here https://www.w3.org/TR/webstorage/), but for simple cases it is match perfect.

Rename the key name in the javascript object

How to rename the key name in the javascript object? That’s easy!

Lets create function to do that:

const renameKey = (object, key, newKey) => {
  const clonedObj = clone(object);
  const targetKey = clonedObj[key];

  delete clonedObj[key];
  clonedObj[newKey] = targetKey;
  return clonedObj;
};

Here is clone function:

const clone = (obj) => Object.assign({}, obj);

Example:

let contact = { 
    id: 1, 
    name: "contact name"
};

contact = renameKey(contact, 'id', 'value');
contact = renameKey(contact, 'name', 'label');

console.log(contact); // { value: 1, label: "contact name" };

Zip two arrays repeating values of the smaller one

So, we have two arrays and we need map each element of the first array to an element from the second. The length of the second array may be less or equal than the first one. What’s if we need to repeat values of the smaller one?

For example, there are two arrays:

const numbers = [1, 2, 3, 4, 5, 6, 7, 8]
const letters = ['a', 'b', 'c']

And we need to get something like:

[ { number: 1, letter: 'a' },
  { number: 2, letter: 'b' },
  { number: 3, letter: 'c' },
  { number: 4, letter: 'a' },
  { number: 5, letter: 'b' },
  { number: 6, letter: 'c' },
  { number: 7, letter: 'a' },
  { number: 8, letter: 'b' } ]

You can use Google or your math skills 😎 and finally will come to this:

const getNormalizedIndex = (index, array) => ((index + array.length) % array.length) % array.length

const numbers_letters = numbers.map((item, index) => {
  return {
    number: item,
    letter: letters[getNormalizedIndex(index, letters)]
  }
})

Run it and you’ll get the expected result.