Skip to content
/ kex Public

Kex is a tiny library for state managenent in JavaScript projects

Notifications You must be signed in to change notification settings

iceekey/kex

Repository files navigation

Kex

Kex is a tiny library for state managenent in JavaScript/TypeScript projects.

  • Small and fast
  • Zero dependencies
  • Async actions, cache, chainable actions out of box
  • Written on Typescript

Installation is simple and plain

  npm i kex

Principles

Just like Redux, Kex has reducers, actions and application state. But this time it's much simplier, because it not rescrict reducers to be pure functions (but they might be if you will).

Actions

Actions is objects that contain type and payload field:

{
  type: string;
  payload?: any;
}

You can dispatch action using dispatch method:

import { createStore } from 'kex';

const store = createStore();
const dispatcher = store.dispatch(action);

// will print current state after action finishes
dispatcher.then(state => console.log); 

Reducers

Reducers appears to be iterators which can return modifiers or promises which should be resolved into modifiers indeed. Modifiers is objects containing information how you want to change the state.

Let's say we have the state:

const state = {
  token: null,
  credentials: {
    login: 'example@mail.com',
    password: '********'
  }
}

and we want to change login field to something else:

import { applyModifiers } from 'kex'; // apply modifiers to specific object

const changeLoginModifier = {
  credentials: {
    login: 'whatever@we-want.com'
  }
}

// this will change state to:
// const state = {
//   token: null,
//   credentials: {
//     login: 'whatever@we-want.com',
//     password: '********'
//   }
// }
applyModifiers(state, changeLoginModifier);

As you can see, modifiers may contain only changes we want to apply to our state rather than whole state. applyModifiers works recursively and merge change of any level right into your state.

A typical reducer may look like this:

const function* reducer(action: KxAction) {
  switch (action.type) {
    ...
    case 'action_name':
      yield { some: 'changes' };
      break;
    ...
    case 'another_action_name':
      // async reducers out of box
      yield Promise.resolve({ more: 'changes' });
      break;
    ...
  }
}

Also Kex allows you to create chainable actions. This means that your reducer may dispatch actions on its own. To do that you should use actions field on the top of your state:

const function* reducerWithChainableAction(action: KxAction) {
  switch (action.type) {
    ...
    case 'chainable_action':
      const { actions } = store.getState()

      yield {
        some: 'changes',
        actions: [
          // if other reducers added thier actions in queue
          ...actions, 
          // after current action perform specified actions
          { type: 'action_name' },
          { type: 'another_action_name', payload: your_payload }
        ]
      };
      break;
    ...
  }
}

actions field creates and clears automatically, all you need to do is add some actions in case you need it.

To set reducers use replaceReducers method:

import { createStore } from 'kex';

const store = createStore();

store.replaceReducers(...reducers);

Reducers order in array actually matters because resolving process in linear. It means that reducers change the state one by one even if they async.

State

You can import storage at any point of your application. Use method getState to get current state:

import { createStore } from 'kex';

const store = createStore();

store.getState();

To modify your state without actions you may use update method:

import { createStore } from 'kex';

const store = createStore();

store.update(modifier);

You can subscribe to state changes using addStorageListener (and cancel the subscription using removeStorageListener method):

import { createStore } from 'kex';

const store = createStore();

const listener = (state, change) => {
  console.log(change.action === null ? 'Changed manually' : `By ${change.action} action`);
  console.log(`Changes: `, change.changes);
};

store.addStorageListener(listener);
store.removeStorageListener(listener);

You also can get history of state change using history() method:

import { store } from 'kex';

store.setHistoryMaxSize(10); // 10 is default value
store.history(); // will return 10 last actions

To clear your state use clear method:

import { store } from 'kex';

store.clear();

update and clear methods will appear in history, but action field will be set to null.

Example

  import { createStore, KxAction } from 'kex';

  const store = createStore();

  store.update({
    counter: 0,
    thisWillNotBeChanged: null
  })

  const function* counterReducer(action: KxAction) {
    const { counter } = kxStore.getState();
    
    switch (action.type) {
      case 'INCREMENT':
        yield {
          counter: counter + 1;
        };
        break;
      case 'ADD': 
        if (action.payload < counter) {
          return;
        }
      
        yield {
          actions: Array(action.payload - counter).fill({ type: 'INCREMENT' })
        }
    }
  }

  store.replaceReducers(counterReducer);

  // {
  //   counter: 10,
  //   thisWillNotBeChanged: null
  // }
  store.dispatch({ type: 'ADD', payload: 10 }).then(console.log);

Cache

Kex also support cache feature (key-value storage inside your state). You can change cache manually using dispatch and update methods but we strongly recommend to use setCache method insted. You can get your cache records using getCache method:

  import { createStore } from 'kex';

  const store = createStore();

  store.setCache('key', 'value'); // save key-value pair in cache (token === undefined)

  // You can use tokens to invalidate cache after token expires or changes
  store.setCache('foo', 'bar', 'token') // save key-value pair in cache (token === 'token')

  /*
   * After this state will look like this:
   *  {
   *    actions: [],
   *    cache: {
   *      key: {
   *        token: undefined,
   *        value: 'value'
   *      },
   *      foo: {
   *        token: 'token',
   *        value: 'bar'
   *      }
   *    }
   *  }
   * 
   */

  store.getCache('wrong_key') // null
  store.getCache('key'); // 'value'
  
  store.getCache('foo'); // null
  store.getCache('foo', 'token') // 'bar'
  store.getCache('foo', 'wrong_token') // null

  // You can change token and value any time
  store.setCache('foo', 'baz', 'token');
  store.getCache('foo', 'token'); // 'baz'

  store.setCache('foo', 'new_baz', 'new_token');
  store.getCache('foo', 'new_token'); // 'new_baz'

Contributing

If you want to contribute to project please contact me. I'm open to discuss the concept.

About

Kex is a tiny library for state managenent in JavaScript projects

Resources

Stars

Watchers

Forks

Packages

No packages published