Michal
Michal

Reputation: 3276

How does technique for making a copy of a function works in JavaScript (Storybook example)

Storybook recommends technique for making a copy of a function:

const Template = args => <Task {...args} />;
export const Default = Template.bind({});

It allows each exported story to set its own properties, but use the same implementation. I understand result but don't get why it has to bind function to an empty object {}

Upvotes: 2

Views: 490

Answers (2)

terrymorse
terrymorse

Reputation: 7086

The above code snippet is using bind() on an arrow function:

const Template = args => <Task {...args} />;
export const Default = Template.bind({});

Using bind on an arrow function does not modify the behavior of the arrow function at all, since the this of an arrow function will always be the scope where the arrow function was defined.

MDN recommends not to use bind on arrow functions MDN - Arrow function expressions: call, apply and bind

The call, apply and bind methods are NOT suitable for Arrow functions -- as they were designed to allow methods to execute within different scopes -- because Arrow functions establish "this" based on the scope the Arrow function is defined within.

const arrowFn = () => {
  console.log('this.foo:', this.foo);
};

window.foo = 'window.foo';
const myObj = {
  foo: 'myObj.foo'
};

const boundFn = arrowFn.bind(myObj);

arrowFn();  // 'window.foo'
boundFn();  // 'window.foo'

Since bind can't modify an arrow function, the only useful thing bind is doing in this example above is creating a new wrapper function that can have its own unique properties.

Therefore, the code from the supplied example:

const Template = (...args) => {};
const Default = Template.bind({});
Default.args = {...};

could be replaced with:

const Template = (...args) => {};
Default = function () { Template(...arguments); }
Default.args = {...};

This second pattern has the advantage of not using bind in a non-standard way.

Upvotes: 1

emi
emi

Reputation: 3080

The full relevant code from the link you provided is:

import React from 'react';

import Task from './Task';

export default {
  component: Task,
  title: 'Task',
};

const Template = args => <Task {...args} />;

export const Default = Template.bind({});
Default.args = {
  task: {
    id: '1',
    title: 'Test Task',
    state: 'TASK_INBOX',
    updatedAt: new Date(2018, 0, 1, 9, 0),
  },
};

export const Pinned = Template.bind({});
Pinned.args = {
  task: {
    ...Default.args.task,
    state: 'TASK_PINNED',
  },
};

Using Template.bind({}) allows for the extra members, such as Default.args = {...}, to be defined differently on each copy and from the original (not used here).

Edit

As already said in the docs from the link:

Template.bind({}) is a standard JavaScript technique for making a copy of a function. We use this technique to allow each exported story to set its own properties, but use the same implementation.

Upvotes: 2

Related Questions