Releases: ctrlplusb/easy-peasy
v2.1.0
v2.0.1
v2.0.0
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
thunk
s 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 anaction
or athunk
- Introduces an
action
helper and requires that actions be declared using this helper - Changes
thunk
helper API.getState
now returns local state to thunk. AddedgetStoreState
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 thunk
s. 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.