7 things I learned adding Jest to my existing JavaScript boilerplate

Here are 7 things that I encountered when trying to add Facebook’s Jest to my existing React JS project.

This project was initially based on Corey House’s boilerplate – React Slingshot. There was nothing wrong with the test setup the boilerplate provided. I just wanted an excuse to try Jest.

1. Jest doesn’t play well with importing stylesheets

Here was the code I was using:

import React from 'react';
import '../styles/about-page.css';

const AboutPage = () => {
  return (

And the associated Jest output:

 FAIL  __tests__/components/AboutPage.react-test.js
  ● Test suite failed to run

    /home/chris/Development/react-registration-demo/src/styles/about-page.css:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){.alt-header {
                                                                                             ^
    SyntaxError: Unexpected token .
      
      at transformAndBuildScript (node_modules/jest-runtime/build/transform.js:284:10)
      at Object.<anonymous> (src/components/AboutPage.react.js:2:27)
      at Object.<anonymous> (__tests__/components/AboutPage.react-test.js:2:44)

To be fair, the output looks a bit nicer in the terminal.

Removing the stylesheet line:

import React from 'react';
// import '../styles/about-page.css';

const AboutPage = () => {
  return (

Now commented out, all passing:

 PASS  __tests__/components/AboutPage.react-test.js
  ✓ Can see header (10ms)

Snapshot Summary
 › 1 snapshot written in 1 test suite.

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 added, 1 total
Time:        1.077s
Ran all test suites.

2. Tests live in the __tests__ directory

The tutorial link doesn’t make it immediately obvious that the location and naming of your test files has an assumptive default config:

(/__tests__/.*|\\.(test|spec))\\.(js|jsx)$)

The boiler plate I am using has the test files in the same directory as the component itself. I followed the boiler plate pattern first, and it didn’t work.

I was confused why my tests wouldn’t run:

> jest

No tests found
  50 files checked.
  testPathDirs: /home/chris/Development/react-registration-demo - 50 matches
  testRegex: (/__tests__/.*|\.(test|spec))\.jsx?$ - 0 matches
  testPathIgnorePatterns: /node_modules/ - 50 matches

Tests need to go in a __tests__ directory, unless you are a regex ninja.

If you do want to overwrite it then you can update your package.json with extra config:

  "jest": {
    "testRegex": "your regex here dot star hash"
  }

I went with their defaults.

Now also note, you wouldn’t have this problem if you had read the landing page and the Docs link. I made the mistake of going direct to the Docs link via Google.

3. You may need to rename things

In React so far I have seen files named like either thing.jsx,  or thing.js. I’ve never seen anyone use thing.react.js.

However, the Jest docs use this convention.

So, having made two mistakes already, I made the switch myself.

Tangible benefit: None(?)

4. Jest ran existing tests just fine

This one threw me.

I had an existing file left over from my purge of the boilerplate demo site. I manually deleted the files as I worked my way through and replaced the boilerplate parts with my own.

One such file that was left was a utility class called mathHelper.spec.js 

Jest ran this just fine:

> jest

 PASS  __tests__/components/AboutPage.react-test.js
 PASS  src/utils/mathHelper.spec.js

Test Suites: 2 passed, 2 total
Tests:       13 passed, 13 total
Snapshots:   1 passed, 1 total
Time:        1.157s

5. Snapshot Testing is a really smart idea

You are kidding me.

Another concept to learn?

When will this learning ever end? Never. Get reading.

Actually though this is fairly simple to understand, and incredibly beneficial.

An example illustrates it best.

Let’s say I have a very basic component:

import React from 'react';

const AboutPage = () => {
  return (
    <div>
      <h2 className="alt-header">About</h2>
      <p>Who wouldn't want to know more?</p>
    </div>
  );
};

export default AboutPage;

And a really simple test:

import React from 'react';
import AboutPage from '../../src/components/AboutPage.react';
import renderer from 'react-test-renderer';

test('Can see header', () => {
  const component = renderer.create(
    <AboutPage />
  );
  let tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});

The first time I run the tests with npm test then Jest will create a snapshot of how this page is expected to look:

exports[`test Can see header 1`] = `
<div>
  <h2
    className="alt-header">
    About
  </h2>
  <p>
    Who wouldn\'t want to know more?
  </p>
</div>
`;

And the test output:

> jest

 PASS  __tests__/components/AboutPage.react-test.js
  ✓ Can see header (9ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 passed, 1 total
Time:        0.827s, estimated 1s
Ran all test suites.

Then, should you make a change to your component in some way which causes the output to change:

import React from 'react';

const AboutPage = () => {
  return (
    <div>
      <h2 className="alt-header">About</h2>
      <p>Changed</p>
    </div>
  );
};

export default AboutPage;

Then re-run:

> jest

 FAIL  __tests__/components/AboutPage.react-test.js
  ● Can see header

    expect(value).toMatchSnapshot()
    
    Received value does not match stored snapshot 1.
    
    - Snapshot
    + Received
    
      <div>
        <h2
          className="alt-header">
          About
        </h2>
        <p>
    -     Who wouldn't want to know more?
    +     Changed
        </p>
      </div>
      
      at Object.<anonymous> (__tests__/components/AboutPage.react-test.js:10:16)
      at process._tickCallback (internal/process/next_tick.js:103:7)

  ✕ Can see header (11ms)

Snapshot Summary
 › 1 snapshot test failed in 1 test suite. Inspect your code changes or run with `npm test -- -u` to update them.

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   1 failed, 1 total
Time:        1.078s
Ran all test suites.
npm ERR! Test failed.  See above for more details.

Again, it looks nicer in the console.

6. You probably want an alias for updating snapshots

Here’s what I use, feel free to differ:

  "scripts": {
    "test": "jest",
    "testu": "npm run test -- -u",
  },

Every time you make a change to your component you likely need to re-update your snapshots. Having an alias is better than the alternative of typing out morse code.

7. There are concerns I only found out about after reading the Snapshot announcement blog post

Dropped in at the bottom of the blog post announcing Snapshot testing is some notes about forthcoming improvements.

This one caught my eye the most:

  • Mocking: The mocking system, especially around manual mocks, is not working well and is confusing. We hope to make it more strict and easier to understand.

Thankfully I did this migration on a new git branch 🙂

An Example of JavaScript Double Negative

javascript double negative
Image credit: https://flic.kr/p/2oNKyk

I remember the first time I saw JavaScript double negative syntax. It confused the heck out of me – so much so, I thought it was a mistake.

Over time, I have seen other developers equally confused. It must get asked in Slack at least once a quarter, if not more.

In case you aren’t sure, let’s quickly cover exactly what we are talking about here:

{ isAuthenticated: !!localStorage.getItem('idToken') }

The point of interest here being the double negative / double bang / !! / double exclamation marks. I put all the alternatives I can think of in, in the hope that it helps others searching for similar.

It’s also worth pointing out that double exclamation marks are not a single operator. I guess that makes it all the more difficult to Google, and understand.

Just Tell Me What It Is

For the impatient, using Javascript double negative / not not will coerce your value to a boolean, and ensure that the output is of type boolean.

To translate that from technobabble:

The first ! will convert a value that isn’t either true or false into a value that is  true or false, and then invert it.

The second ! will invert it again.

Confusing?

An example illustrates a potential use case, which hopefully will make it slightly easier to understand.

Just Show Me An Example

Here is a real world example from an authentication reducer I am currently working on in a React / Redux project.

Let’s look at the code as it stands before adding in the double negatives:

import {
  LOGIN__FAILED,
  LOGIN__SUCCESS,
  LOGOUT__SUCCESS
} from '../constants/ActionTypes';

export default function auth(state = {
  sendingRequest: false,
  isAuthenticated: localStorage.getItem('idToken')
}, action) {

  switch (action.type) {

    case LOGIN__SUCCESS:
      return Object.assign({}, state, {
        isAuthenticated: true,
        userId: action.userId,
      });

    case LOGIN__FAILED:
      return Object.assign({}, state, {
        isAuthenticated: false,
        errorMessage: action.errorMsg
      });

    case LOGOUT__SUCCESS:
      return Object.assign({}, state, {
        isAuthenticated: false
      });

    default:
      return state;
  }
}

Most of this is not that interesting in the context of this particular example.

I have highlighted line 9 where localStorage is used to retrieve the user’s idToken, should it exist.

There is a bug in this code.

The default case of the switch statement means that when a user first loads the page / before they have ever logged in, the default state would return:

{
  sendingRequest: false,
  isAuthenticated: null
}

Instead, I want it to be:

{
  sendingRequest: false,
  isAuthenticated: false
}

I could use a ternary operator to fix this:

export default function auth(state = {
  sendingRequest: false,
  isAuthenticated: localStorage.getItem('idToken') ? true : false
}, action) {

And this would work, but WebStorm moans at me that I can do better:

webstorm-can-be-simplified
WebStorm telling me I suck

I make the change:

export default function auth(state = {
  sendingRequest: false,
  isAuthenticated: !!localStorage.getItem('idToken')
}, action) {

Why does this work?

Because by localStorage.getItem(‘idToken’) is going to either return me a string (containing my locally saved idToken), or as we have already seen, a null.

If we check whether a string is not (!) something, we would get false – because a string is definitely something.

It therefore stands that if a string is not not something, then it must be true.

Likewise, if we check if a null is not (!) something, we would get true – because a null is definitely nothing.

It equally therefore must stand that if a null  is not not nothing, then it must be false.

Still confused? Don’t feel down about it. It took me ages to grasp this, and it’s so unusual that it really won’t come up that frequently.

I would even go as far as to say consider sticking with the ternary operator approach. It is simply easier for most developers to immediately understand, even if WebStorm moans about it.

Be Careful What You console.log

I caught myself out.

What’s worse – I thought I was helping myself in doing so!

Let me set the scene:

I have a project where I need to find the most recent file in a directory.

There’s probably a hundred or more ways to do this, but my way – in this instance – goes a little something like this:

readDirectory(data.filePath)
  .then(dirContents => {
    return findMostRecentFileInList(dirContents);
  })
  .then(mostRecentFileName => { // etc

It’s a node JS project btw.

Each of the files in the directory would be in the format {timestamp}.csv .

There exists a possibility that the files may be restored from backup, therefore I couldn’t rely on the “created at” dates.

Anyway, this all works quite well.

The directory contents during test looks something like this:

➜  my-dir git:(my-branch) ✗ ls -b1    
1471809864936.csv
1471896885574.csv
1471896978085.csv
1471896985167.csv
1471897173301.csv
1471897251818.csv

And here’s the contents of the findMostRecentFileInList  function:

/**
 * Expects to receive a list of files in the format ${timestamp}.ext
 * Sorts the files on file name, returns the second most recent
 * The first most recent would be the new file
 */
const findMostRecentFileInList = (list) => {
  if (!list || Array !== list.constructor || list.length === 0){
    console.log('findMostRecentFileInList returning false');
    return false;
  }
  console.log('findMostRecentFileInList - returning', list.reverse().slice(1,2).toString());
  return list.reverse().slice(1,2).toString();
};

Full marks if you have already spotted the problem.

If not, no worries, allow me to explain.

We can disregard the vast majority of this function and just focus on two lines:

console.log(list.reverse().slice(1,2).toString());
return list.reverse().slice(1,2).toString();

What was confusing the heck out of me was that in the next then statement, the value I would receive would be incorrect. Yet the console log output only one step prior was definitely right.

Welp, the reason is as amusing as it is frustrating. It’s exactly the sort of thing that catches out many a developer (I would bet).

What happens here is that by calling list.reverse() in the console log statement, the list is correctly reversed.

The slice function takes the value I need and that’s correctly output to the screen.

So far, so good.

Then, on the very next line, I take the reversed list and reverse it again… putting it back into the original order. Then take a slice from that, and return that instead.

Oops.

It’s so obvious when it’s spelled out to you, but debugging that sort of thing is how devs like me lose tens of minutes, if not hours of time.

These sorts of things tend to catch me out late at night. Maybe I should work earlier in the mornings.

How I Fixed: TypeError: ‘null’ is not an object (evaluating ‘currentSpec.$injector’)

 

I hit upon a problem in testing an Angular project this week that had me stumped for a while. The problem was this:

Every time I ran the test suite as a whole, they failed.

But if I ran each test file on its own… individually, they would pass.

I find more and more of my time lately is spent dealing with these sorts of things – they aren’t ‘development’ tasks, just annoyances that keep me from doing the thing I enjoy most – writing code.

Anyway, here’s the error:

INFO [PhantomJS 1.9.8 (Linux 0.0.0)]: Connected on socket DGb_Xn6WLWQNnEEkUwtk with id 45807911
PhantomJS 1.9.8 (Linux 0.0.0) LOG: 'no config'
 
PhantomJS 1.9.8 (Linux 0.0.0) LOG: 'no config'
 
PhantomJS 1.9.8 (Linux 0.0.0) LOG: 'no config'
 
PhantomJS 1.9.8 (Linux 0.0.0) UrlManager should have get function FAILED
        TypeError: 'null' is not an object (evaluating 'currentSpec.$injector')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23390:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2330:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'null' is not an object (evaluating 'currentSpec.$injector')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23390:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2330:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'null' is not an object (evaluating 'currentSpec.$injector')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23390:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2330:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'null' is not an object (evaluating 'currentSpec.$modules')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23514:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2454:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'undefined' is not an object (evaluating 'UrlManager.get')
            at /tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:42035:0 <- /my/project/src/components/UrlManager/spec/UrlManagerSpec.js:26:0
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
PhantomJS 1.9.8 (Linux 0.0.0) UrlManager should return object for the others FAILED
        TypeError: 'null' is not an object (evaluating 'currentSpec.$injector')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23390:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2330:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'null' is not an object (evaluating 'currentSpec.$injector')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23390:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2330:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'null' is not an object (evaluating 'currentSpec.$injector')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23390:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2330:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'null' is not an object (evaluating 'currentSpec.$modules')
            at workFn (/tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:23514:0 <- /my/project/node_modules/angular-mocks/angular-mocks.js:2454:0)
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
            at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
        TypeError: 'undefined' is not an object (evaluating 'UrlManager.get')
            at /tmp/7b8601aaed330a593345d271f7b468335eb33d59.browserify:42053:0 <- /my/project/src/components/UrlManager/spec/UrlManagerSpec.js:44:0
            at /my/project/node_modules/karma-jasmine/lib/boot.js:126
           at /my/project/node_modules/karma-jasmine/lib/adapter.js:171
            at http://localhost:9876/karma.js:182
            at http://localhost:9876/context.html:257
PhantomJS 1.9.8 (Linux 0.0.0): Executed 7 of 7 (2 FAILED) (0.415 secs / 0.026 secs)
 
=============================== Coverage summary ===============================
Statements   : 17.48% ( 482/2758 )
Branches     : 4.14% ( 40/967 )
Functions    : 4.65% ( 30/645 )
Lines        : 17.62% ( 477/2707 )
================================================================================
[13:41:23] 'test' errored after 8.21 s
[13:41:23] Error in plugin 'test'
Message:
    Karma test returned 1
blimpyboy@project-dev1:~/Development/project$ ^C
blimpyboy@project-dev1:~/Development/project$ ^C
blimpyboy@project-dev1:~/Development/project$ gulp test
[13:42:58] Warning: gulp version mismatch:
[13:42:58] Global gulp is 3.9.0
[13:42:58] Local gulp is 3.8.11
[13:42:59] Using gulpfile ~/Development/project/gulpfile.js
[13:42:59] Starting 'test'...
INFO [framework.browserify]: Paths to browserify
    /my/project/src/components/PubSub/spec/**/*.js
    /my/project/src/components/UrlManager/spec/**/*.js
INFO [framework.browserify]: Browserified in 6543ms, 6524kB
INFO [karma]: Karma v0.12.37 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.8 (Linux 0.0.0)]: Connected on socket VrUi5MBGOl0J1oHWVKNw with id 50398227
PhantomJS 1.9.8 (Linux 0.0.0) LOG: 'no config'
 
PhantomJS 1.9.8 (Linux 0.0.0) LOG: 'no config'
 
PhantomJS 1.9.8 (Linux 0.0.0) LOG: 'no config'
 
PhantomJS 1.9.8 (Linux 0.0.0): Executed 7 of 7 SUCCESS (0.056 secs / 0.035 secs)
 
=============================== Coverage summary ===============================
Statements   : 20.01% ( 552/2758 )
Branches     : 5.07% ( 49/967 )
Functions    : 6.98% ( 45/645 )
Lines        : 20.21% ( 547/2707 )
================================================================================
[13:43:07] Finished 'test' after 8.1 s
blimpyboy@project-dev1:~/Development/project$

I’m aware the coverage isn’t so good – but actually this is not the true coverage as I’d stripped out a whole bunch of tests by bastardising the karma.conf.js file to try and isolate the problem. No… seriously, I promise 🙂

Anyway, it turned out that the solution to this was actually pretty simple.

Of course, nearly all solutions to programming problem seem simple once you have figured out the problem. Hindsight is such a wonderful thing.

But in this case, the problem was that a bunch of variables had been declared inside on the of describe blocks:

describe("SomeModule module", function () {

    beforeEach(angular.mock.module('Some.Module'));

    var angular = require('angular');
    var scope;
    require('../../../scripts/app.js');
    require('angular-mocks');

    var SomeModule;

And the solution was to simply move all the setup stuff outside of the describe block:

var angular = require('angular');
var scope;
require('../../../scripts/app.js');
require('angular-mocks');

var SomeModule;

describe("SomeModule module", function () {

    beforeEach(angular.mock.module('Some.Module'));

An easy fix.

The real annoyance here was that I went through this whole project alphabetically, and this particular module began with the letter ‘P’, so I’d been through over half the code before I spotted it. Hours I will never get back.

Still, it’s fixed now, and hopefully now you can save a few hours if you ever suffer from this problem yourself.

 

Why I Find JavaScript’s ES6 Destructuring So Fascinating

I spend a lot of time in PHP land.

PHP is a very useful and competent language, but I feel it’s fair to say it lacks somewhat in syntactical grace.

JavaScript’s ES6 on the other hand, is very elegant (to my eyes). This is a snippet from a React tutorial I have been following recently.

  const {
    isFetching,
    lastUpdated,
    items: posts
    } = postsBySubreddit[selectedSubreddit] || {
    isFetching: true,
    items: []
  };

There’s so much going on here.

As a recent migrant to ES6, I find this as intriguing as I do confusing.

The primary concept at work here is ES6 Destructuring. This is a really interesting technique to pull multiple pieces of data from arrays or objects in one go.

I found the concept initially confusing. I read this as a const  that is an object but has no variable name? Not quite.

It’s somewhat easier to understand this statement if we break it down one step further:

  const {
    isFetching,
    lastUpdated,
    items: posts
    } = postsBySubreddit[selectedSubreddit];

I still feel this is complicated for someone, like me, who is new to Destructuring.

Let’s take an even simpler example:

const {a} = {a: 1, b: 2};

This looks a bit easier, a bit more understandable, I feel.

Here we have a regular JavaScript object: {a: 1, b: 2}

We want to create ourselves a constant containing the value of a from this object. A test might illustrate this a little better than words:

it('is possible to grab "a" from our object', () => {
  const ourObject = {a: 1, b: 2};
  let a = ourObject.a; 
  assert.equal(a, 1);
});

Destructuring gives us a short hand notation for this same concept:

it('is possible to grab "a" from our object in a more concise way', () => {
  const {a} = {a: 1, b: 2};
  assert.equal(a, 1);
});

By using the same key name inside the {} ‘s as that which exists on the object, ES6 will go ahead and create us a constant, called a with the value of 1. It essentially pulls out the value from the object and creates a variable / constant for us. Two steps, combined into one. This is one of those things I have immediately started to miss the most when working in PHP.

But what if we don’t want the new constant to be called a, but we do want it to still contain the value of a. Of course, ES6 has you covered here too:

it('is possible to rename "a"', () => {
  const {a: somethingElse} = {a: 1, b: 2};
  assert.equal(somethingElse, 1);
});

Seems a little back to front, but it does make sense. Pull out a as we’ve already covered, and re-name it to somethingElse.

You can see this in action in the React tutorial snippet:

  const {
    isFetching,
    lastUpdated,
    items: posts
    } = postsBySubreddit[selectedSubreddit];

Here, items exists in the original data, and whilst we do want to use the value of items, we want to use a different reference for it – posts.

There are other things we could do with destructuring assignment, it really is quite an awesome concept.

Now we know about these two concepts, let’s take another look at the example:

  const {
    isFetching,
    lastUpdated,
    items: posts
    } = postsBySubreddit[selectedSubreddit];

So we can see both of the above (pulling out by name, and pull-out-and-rename) happening in one statement here.

I found the postsBySubreddit name really confused me. If you can, disregard the name, and simply think of it as an object:

someObject[dynamicKey]

Then for me, it becomes a little easier to reason about:

  it('is possible to use a dynamic key', () => {
    
    let dynamicKey = 'key2';

    const someObject = {
      key1: 'a',
      key2: 'b',
      key3: 'c'
    };
    
    assert.equal(someObject[dynamicKey], 'b');
  });

postsBySubreddit is simply an object with potentially multiple keys.Each key will be the name of a subreddit: ‘php’, ‘reactjs’, ‘programming’ and so on.

We aren’t hard-coding the value of the key we need. We are instead asking for a key using the variable named selectedSubreddit, or dynamicKey in my example.

Don’t just take my word for this though, do look at the code. Unfortunately, I can’t link directly to the line(s) in question, but a quick ctrl+f on the linked page for ‘postsBySubreddit’ will get you to where you need to be.

selectedSubreddit will be one of these keys / subreddits. And all subreddit data will be an array, called items. But because we know that destructuring can rename a property for us, we can take advantage of this to rename items to posts .

isFetching and lastUpdated are both straightforward (in as much as any of this is straightforward), in that they are keys we want to create constants for.

Phew, that’s a lot happening in one statement.

But, remember, we actually broke this line down even further:

  const {
    isFetching,
    lastUpdated,
    items: posts
    } = postsBySubreddit[selectedSubreddit] 
  || {
    isFetching: true,
    items: []
  };

At this stage, we are really only concerned with the bit we haven’t yet covered, so I’ve reformatted it slightly to make it more obvious.

So we’ve done all this destructuring… OR (||) if we are unable to access postsBySubreddit[selectedSubreddit]  then create two constants:

isFetching: true,
items: []

Which would leave lastUpdated as undefined.

But don’t just take my word for it. Have a play around with this code and get a feel for what it is doing. Destructuring is definitely something you will start using more and more, but it’s initially confusing for sure.

JavaScript is moving fast (although I have heard some say that this is slowing down) and if you’re still finding your feet with PHP (or any other language) then I’d advise not to dig to deeply into ES6 just yet.

I’d argue that the transition to ES6 is going to be so challenging for some developers that we may possibly end up in a Python 2 / 3 situation in JavaScript land in the next few years. Which is why I believe the current form of JavaScript will be around for years to come.

Whichever form of JavaScript you choose to use, take the time to dig into the more functional concepts of JavaScript. It will truly help your programming – including your PHP – skills grow.