Skip to content

v2.0.0

Compare
Choose a tag to compare
@ctrlplusb ctrlplusb released this 12 Mar 18:30
· 514 commits to master since this release

The V1 API finally reached a place where any significant new features/changes would become a breaking change. In order to avoid breaking change fatigue we have tried to group all the breaking changes we would like to make in this singular v2 release, whilst ensuring that the migration path from v1 to v2 would not be painful. The breaking changes are detailed below along with the changes required to upgrade.

Some of the highlights of v2 includes:

  • Rewrites internals, optimising it dramatically, including an optimisation fix on selectors
  • thunks that are fired and will be displayed in redux dev tools to indicate the started/completed/failed states of them
  • Allows models to be dynamically added/removed to the store.
  • Removes APIs that were announced as deprecated in v1
  • Allows the listen implementations to trigger an action or a thunk
  • Introduces an action helper and requires that actions be declared using this helper
  • Changes thunk helper API. getState now returns local state to thunk. Added getStoreState which returns full store state.

You will need to follow the notes for the breaking changes below if upgrading a v1 project to v2. We have tried to output helpful console messages where we can to help with this migration.

New Feature: Thunk action firing

In v1 we would dispatch actions to represent the start/complete of a thunk. This was done with the intention of aiding debugging only, and was disabled if you disabled dev tools via the createStore config.

We have decided to make these action dispatches an official part of the API. They are now guaranteed to fire even if the dev tools are disabled. In addition to this we have introdcued a third type of action that can fire; specifically if a thunk fails. If an error is thrown in the processing of one of your thunks an action will fire to indicate that this is the case. The action will contain an additional parameter called error, which will be an object containing the message and stack of the error.

const store = createStore({
  doSomething: thunk(async () => {
    throw new Error('oh noes');
  }),
});

// Dispatching this thunk action results in a failure
store.dispatch.foo.doSomething('hi');

// Check your dev tools and you should see actions dispatched like this:
// [
//   { type: '@thunk.doSomething(started)', payload: 'hi' },
//   { 
//     type: '@thunk.doSomething(failed)', 
//     payload: 'hi', 
//     error: { message: 'oh noes', stack: '...' } 
//   }
// ]

New Feature: Dynamically add/remove models to/from your store

This is in response to #29, where the request was made to be able to dynamically add models, which can help for advanced/large applications that are code split.

We have added two new APIs which get exposed on the store allowing you to do this.

const store = createStore({
  counter: {
    count: 0,
  },
});

// Adding a model
store.addModel('todos', {
  items: [],
  add: action((state, payload) => {
    state.items.push(payload);
  })
});

// You can now use the newly added model
store.getState().todos.items;
store.dispatch.todos.add('Upgrade to v2');

// You can also remove models
store.removeModel('todos');

Breaking Change: Actions

Previous way of defining actions (v1):

const model = {
  count: 0,
  increment: (state) => {
    state.count += 1;
  }
};

New way of defining actions (v2):

import { action } from 'easy-peasy';

const model = {
  count: 0,
  increment: action((state) => {
    state.count += 1;
  })
};

Breaking Change: Listeners

Previous way of defining listeners (v1):

import { listen, action } from 'easy-peasy';

const notificationModel = {
  msg: '',
  set: action((state, payload) => {
    state.msg = payload;
  }),
  listeners: listen((on) => {
    //                         πŸ‘‡ previous syntax
	on(userModel.register, (actions) => {
       actions.set('User logged in');
    });
  })
};

New way of defining listeners (v2):

import { listen, thunk } from 'easy-peasy';

const notificationModel = {
  msg: '',
  set: action((state, payload) => {
    state.msg = payload;
  }),
  listeners: listen((on) => {
    //                      πŸ‘‡ new syntax
	on(userModel.register, thunk((actions) => {
       actions.set('User logged in');
    }));
  })
};

As you can see you explicitly have to declare the handler as a thunk. V2 also allows you to declare your listeners as an action:

import { listen, thunk } from 'easy-peasy';

const notificationModel = {
  msg: '',
  listeners: listen((on) => {
    //                      πŸ‘‡ new syntax
	on(userModel.register, action((state) => {
       state.msg = 'User logged in';
    }));
  })
};

This can be useful as an alternative syntax, producing more concise code.

Breaking Change: Thunk getState/getStoreState

We have updated the helpers API for thunks. The getState helper on a thunk will now return the local state at where the thunk is mounted. An additional helper named getStoreState has been added which will continue to return the full store state.

This is an improvement with the intention of allowing models to be more self contained. It promotes better testability and allows for easier writing of helpers that you can use to boostrap models.

In order to fix your code...

Before:

import { thunk } from 'easy-peasy';

const model = {
  counter: {
    count: 0,
    doSomething: thunk((actions, payload, { getState }) => {
      getState().counter.count
    })
  }
};

After:

import { thunk } from 'easy-peasy';

const model = {
  counter: {
    count: 0,
    doSomething: thunk((actions, payload, { getState }) => {
      getState().count
    })
  }
};

Or if you need state from another branch of your model:

import { thunk } from 'easy-peasy';

const model = {
  user: {
    name: 'bob'
  },
  audit: {
    doSomething: thunk((actions, payload, { getStoreState }) => {
      getStoreState().user.name
    })
  }
};

We don't recommend reaching across models though, and instead recommend using listeners instead.