Skip to content

Releases: ctrlplusb/easy-peasy

v3.0.0

23 Jul 15:53
Compare
Choose a tag to compare

v3 is considered the realisation of the "final" Easy Peasy API - taking all the evolution and learning from v2 to produce a long term stable API that we will commit to supporting and will do our best to avoid breaking changes moving forward.

New Features

Hot reloading support

Hot reloading is supported via the store.reconfigure(model) API. See #168

New actionOn and thunkOn APIs

These are the new and only APIs by which to define action/thunk listeners with.

The v3 website has been updated with tutorials and API docs introducing these APIs.

We are really sorry about the churn around listener APIs! This API was driven by community feedback so feeling far better about it. πŸ‘

Breaking Changes

Removed deprecated APIs

Thunks can be either asynchronous and synchronous

Using async/await or returning a Promise from a thunk will maintain its previous async behaviour.

However, if you do neither of the above your thunk will be executed synchronously. Therefore you can now get eager updates to your state if all you do is dispatch actions within your thunk. This can be handy for encapsulating logic based action dispatching.

For example

addProduct: thunk((actions, payload) => {
  switch (payload.type) {
    case 'SHOES': 
		actions.addShoe(payload);
        break;
    case 'VEGETABLE':
        // ...
  }
});

Returning immutable state from actions

If you prefer to return new immutable state from your actions, rather than mutating the state, you need to set the new disableImmer flag.

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

const model = {
  todos: [],
  addTodo: action((state, payload) => {
    // πŸ‘‡ new immutable state returned
    return [...state, payload];
  })
}

const store = createStore(model, {
  disableImmer: true // πŸ‘ˆ set the flag
})

Failing to disable immer may result in strange errors if you are using computed properties.

computed

In order to optimise the Typescript experience we have made a fairly small change to the computed API. If you wish to use state resolvers, these now need to be defined as the first argument.

Before

const basketModel = {
  productIds: [],
  products: computed(
    (productIds, products) => productIds.map(id => products[id]),
    [
      state => state.productIds,
      (state, storeState) => storeState.products.items
    ]
  )
};

After

const basketModel = {
  productIds: [],
  products: computed(
    [
      state => state.productIds,
      (state, storeState) => storeState.products.items
    ],
    (productIds, products) => productIds.map(id => products[id])
  )
};

Computed properties not using state resolvers remain unchanged.

useStoreState API update

useStoreState(previouslyuseStore) no longer needs/accepts the dependencies 2nd argument. Your state will get mapped correctly if they use external values, like props, within the mapState` function.

Typescript

We officially support >= [email protected]. Although we recommend using the latest version ([email protected] at the time of writing), in order to ensure you are up to date with the latest bug fixes.

Create React App users can just install [email protected] as a dev dependency and the CRA build system will use that version. You may get a warning printed to your console, however, we experienced no issues with this. πŸ‘

Note: using a lower version of TypeScript 3.x may still work, however, you may have issues.

Hooks

You will have noted above that the useStoreState, useStoreActions, and useStoreDispatch are no longer attached to the store instance. You need to use the createTypedHooks helper instead.

import { createTypedHooks } from 'easy-peasy';
import { StoreModel } from './model';

const { useStoreActions, useStoreState, useStoreDispatch } = createTypedHooks<StoreModel>();

export default {
  useStoreActions,
  useStoreState,
  useStoreDispatch
}

Computed

The Computed type no longer requires you to define the types for state resolvers. These will automatically be inferred.

Before

interface BasketModel {
  productIds: string[];
  products: Computed<
    BasketModel, 
    Product[], 
    ResolvedState2<string[], Product[]>, 
    StoreModel
   >
}

After

interface BasketModel {
  productIds: string[];
  products: Computed<
    BasketModel, 
    Product[],
    StoreModel
   >
}

Commits

  • Update overview.md: f5fbe87
  • Removes deprecated code: ba955a5
  • Removes unused assets: 0a06ee8
  • Cleans up and organises the typescript definitions: 78dc4d4
  • Adds depth limit to State and Actions typescript definitions: 55375dc
  • Fixes index signatures on state: 7ce3188
  • Dispatch no longer has actions bound to it and fixes to types: ff6cd76
  • Removes unused deps and bumps version: 858c96b
  • Adds yarnrc: 0bdbdad
  • Fixes thunk payloads: 8b8bdce
  • Bumps version: bff2d9c
  • Adds test case for state any: 7545e63
  • Breaking change listenTo actions now resolved via callback funciton: 8086cb2
  • Progress on new tutorial: 70287db
  • Updates docs: 4b30b9f
  • Updates website: 754d6a2
  • Website updates: 85e8f7b
  • Fixes useStoreState so that an update gets handled in same render cycle across hook instances: 6f2826f
  • Bumps version: 56c871f
  • Fixes multi hook render cycle issue: 42dc2e1
  • Bumps version: 09ac40f
  • Removes the need to define dependencies on useStoreState: 4cb3ec3
  • Bumps version: cd14db1
  • Removes instance of useStoreState dependencies: 1ff97f7
  • Updates deps: 6145efa
  • Website updates: 75ce14c
  • Reverts the change that removed bound action creators on the stores dispatch: e8aa7bd
  • Fixes action name helper typings: d37943d
  • Bumps version: de12c2c
  • Fixes store so it does not kill class based properties: 1c51bf7
  • Bumps version: b3ad4eb
  • Updates website: 8c4ffa2
  • Installs v3 of ts-toolbelt and bumps version: d926ce3
  • Bumps version: e300c57
  • Creates a simplified immer produce and fixes debug helper: 461d42e
  • Adds an additional test case for typescript generic interface models: 3f82175
  • Bumps version: 7a382e8
  • Adds new listeners APIs as per #247: #251
  • Updates ts-toolbelt.: d93a01c
  • Moves prop-types from peer deps to deps: ca321b2
  • Updates to Typscript 3.5.3: 39b80a3
  • Removes prop-types from dev deps: 88aed9a
  • Upgrades deps: f77cb49
  • Adds a comment to a typescript test case: 925c68c
  • Adds createTypedHooks API back: 3a16e10
  • Bumps version: 332c80b
  • Updates website: b23993a
  • Updates website: 49e4f25
  • Adds the ability to reconfigur...
Read more

v2.6.5

28 Jun 21:52
Compare
Choose a tag to compare

Patches

  • Updates typescript definitions to play a bit nicer with generic model interfaces: f526916
  • Updates to stable immer-peasy: 3144e24

v2.6.4

27 Jun 22:27
Compare
Choose a tag to compare

Patches

  • Fixes number index signatures and enumerable computed props: a74c2b9

v2.6.3

24 Jun 22:56
Compare
Choose a tag to compare

Patches

  • Minor perf improvement on computed property binding: 7316bfe

v2.6.2

24 Jun 22:43
Compare
Choose a tag to compare

Patches

  • Fixes computed property nested state updates: b5d99a7

v2.6.1

24 Jun 22:03
Compare
Choose a tag to compare

Patches

  • Updates docs: 4392ecf
  • Update usage-with-react-redux.md: 261bc7b
  • Fixes nested computed properties issue: b6778b1
  • Fixes nested computed properties issue: 29ec92a

v2.6.0

24 Jun 17:02
Compare
Choose a tag to compare

Deprecation Release

My sincere apologies around the churn of APIs concerning derived data. The original select API had performance issues which were attempted to be addressed by the selector API. Unfortunately the selector helper may have been too eagerly released. It proved to create quite a bit of friction in terms of it's DX. I open an RFC to try and address these concerns and eventually the computed API was born. The computed API provides the same DX as the select API whilst addressing the performance issues. It also provides additional features in that you can isolate state and even derive against the entire store state. I humbly apologise if you had already migrated to the selector API, but kindly ask that you now migrate to the computed API.

I am extremely happy with the Easy Peasy API as it stands. I find that it addresses most of my concerns around global state, whilst still having a very intuitive/palatable API. From here on out I plan to cut v3 of the library which will remove all the deprecated APIs, which will drop the bundle size and open up some great performance optimisations.

Migration

Migration from select to computed

The migration from select to computed is very straight forward. You can simply do a find and replace on select and replace it with computed.

Before

import { select } from 'easy-peasy';

const sessionModel = {
  user: null,
  isLoggedIn: select(state => state.user != null)
}

After

import { computed } from 'easy-peasy';

const sessionModel = {
  user: null,
  isLoggedIn: computed(state => state.user != null)
}

If you are using Typescript you can do a search and replace on Select, replacing it with Computed.

Before

import { Select } from 'easy-peasy';

interface SessionModel {
  user: User | null;
  isLoggedIn: Select<SessionModel, boolean>;
}

After

import { Computed } from 'easy-peasy';

interface SessionModel {
  user: User | null;
  isLoggedIn: Computed<SessionModel, boolean>;
}

Migration from selector to computed

Coming soon...

Release Notes

Minor Changes

  • Adds computed API: 67813a2
    • Fixes and refactors types for computed: 94106ab
    • Fixes accessing computed properties in actions: 306ebf4
  • Adds memo helper: ab7e076

Patches

v2.5.0

18 Jun 17:03
Compare
Choose a tag to compare

Minor Changes

  • Adds the ability to listen to multiple targets: 4e17514

Closes #205 - thanks @jmyrland for requesting this feature. πŸ‘

v2.4.0

14 Jun 13:59
Compare
Choose a tag to compare

Big Release

This release is the culmination of a lot of consideration over the APIs as they had evolved up to v2.3.0. I took the time to consider the parts of the API which I felt were ambiguous, inconsistent, ornot in line with the "easy peasy" premise.

Instead of going for a big bang breaking change release I have aimed for a minor release, and have subsequently marked a few of the APIs as being deprecated. I highly encourage you to migrate to the new APIs as soon as you can.

The website itself is a work in progress. Writing docs is really hard. Especially trying to keep them clear and consistent. I've gotten them to a base level for now, but in my opinion they still need to be taken much further. The Typescript and Testing sections are notable sections that lack completion. I'll get to them, I promise.

New Website

https://easy-peasy.now.sh

πŸŽ‰

selector

❗️❗️❗️ THIS HELPER HAS SINCE BEEN DEPRECATED ❗️❗️❗️
https://github.com/ctrlplusb/easy-peasy/releases/tag/v2.6.0

This API deprecates the previous select API. It addresses some performance concerns and introduces runtime arguments as a first class citizen within the API.

Migrating your code from select to selector

Before:

const todosModel = {
  items: [],
  firstTodo: select(state => 
    state.items.length > 0 ? state.items[0] : undefined
  )
}

After:

import { selector } from 'easy-peasy';

const model = {
  todos: [],
  firstTodo: selector(
    [state => state.todos],
    (resolvedState) => {
      const [todos] = resolvedState;
      return todos.length > 0 ? todos[0] : undefined;
    }
  )
}

The above looks more verbose, but it provides very useful optimisations that will allow your applications to scale. Please read the full docs on the new API here.

debug

This new API allows you to unwrap your state within your actions so that you can console.log them etc.

import { debug } from 'easy-peasy';

const model = {
  myAction: action((state, payload) => {
    console.log(debug(state)); // πŸ‘ˆ prints the "native" state representation
  })
};

action and thunk listeners

Actions and thunks have been extended to allow a configuration when defining them. Both of them support a listenTo configuration value. This value allows you to configure the action or thunk to listen to a target action or thunk.

This API deprecates the previous listen API. We chose to do this deprecate the listen API as this action/thunk based listener implementation provides us with the following benefits:

  1. The ability to see our listener actions/thunks being dispatched in the dev tools
  2. The ability to short circuit the underlying reducer, providing performance benefits (this will be realised when we move to v3 and remove the deprecated APIs)
  3. The ability to more easily test our listeners as we can manually dispatch them in our tests which allows us to test them in much the same manner as we would our standard actions and thunks.

Migrating your code from listen to listener action and thunk

Before:

const todosModel = {
  items: [],
  addTodo: action((state, payload) => {
    state.items.push(payload);
  })
};

const auditModel = {
  log: [],
  listeners: listen(on => {
    on(todosModel.addTodo, action((state, payload) => {
      state.logs.push(`Added todo: ${payload}`);
    });
    on(todosModel.addTodo, thunk((actions, payload) => {
	  console.log(`Added todo: ${payload}`);
    });
  })
};

After:

const todosModel = {
  items: [],
  addTodo: action((state, payload) => {
    state.items.push(payload);
  })
};

const auditModel = {
  log: [],
  onAddTodoActionListener: action(
    (state, payload) => {
      state.logs.push(`Added todo: ${payload}`);
    },
    { listenTo: todosModel.addTodo }
  ),
  onAddTodoThunkListener: thunk(
    (actions, payload) => {
	  console.log(`Added todo: ${payload}`);
    },
    { listenTo: todosModel.addTodo }
  )
  })
};

Hooks

We have created new aliases for the hooks. The previous ones are considered deprecated.

Instead of useActions, please use useStoreActions.

Instead of useDispatch, please use useStoreDispatch.

Instead of useStore, please use useStoreState.

Migrating your code to new hooks

Before:

import { useActions, useDispatch, useStore } from 'easy-peasy';

function MyComponent() {
  const addTodo = useActions(actions => actions.todos.addTodo);
  const todos = useStore(state => state.todos.items);
  const dispatch = useDispatch();
}

After:

import { useStoreActions, useStoreDispatch, useStoreState } from 'easy-peasy';

function MyComponent() {
  const addTodo = useStoreActions(actions => actions.todos.addTodo);
  const todos = useStoreState(state => state.todos.items);
  const dispatch = useStoreDispatch();
}

createComponentStore

We have introduced a new API that allows you to create a store for a component.

createContextStore

We have introduced a new API that allows you to create multiple shared stores.

Store

The store instance has a few new APIs and some deprecated ones against it.

We have added the following:

  • getActions (Function)

    Returns the actions of your store.

  • useStoreActions (Function)

    The useStoreActions hook. This is typically useful when using Typescript with Easy Peasy, as this hook will be typed against your store.

  • useStoreDispatch (Function)

    The useStoreDispatch hook. This is typically useful when using Typescript with Easy Peasy, as this hook will be typed against your store.

  • useStoreState (Function)

    The useStoreState hook. This is typically useful when using Typescript with Easy Peasy, as this hook will be typed against your store.

By attaching the hooks to the store this plays really nicely with Typescript as the hooks will naturally have all the typing information available on them. This deprecates the createTypedHooks API.

The following are considered deprecated:

  • triggerListener (Function)

    Allows you to trigger a listen registration of your model with the provided action.

  • triggerListeners (Function)

    Allows you to trigger all registered listeners across the store that are listening to the provided action.

As the listen API is deprecated, the above functions that allow triggering of them are also deprecated.

v2.3.3

04 Jun 13:38
Compare
Choose a tag to compare

No feature release.

v2.3.2 didn't publish correctly to npm.