2.1.1 • Published 4 years ago

clearx v2.1.1

Weekly downloads
128
License
Apache-2.0
Repository
github
Last release
4 years ago

Table of Contents

Introduction:

ClearX provides an alternative & very simple way to manage the global state for the Frontend application. Check this link for a basic Todo App demo or check out Todo example section

Advantages:
  • Insanely simple to use. Fast & small (3kb)
  • Re-renders UI components only when dependent data is changed in Store
  • The store can be accessed, changed from anywhere in an application using an expressive API
  • The store is a plain reactive JS Object.
  • No limitations for adding new properties to the existing store after creation
  • Local state and global store can be used together. No restrictions. No limit on the number of stores. Store data is reversible & cloneable
  • Makes it easy to work with 3rd party libraries that have no react flavour in them
  • Helps to write tree-shaking friendly codebase.
  • Compatible with React, Preact, and Inferno without any configurational changes.

It's this simple:

Store.bind({
  paths: {
    fileOne: ['file', 'one']
  },
  to: this
})

Note: Earlier versions used slice method. It will work without any problem. But the bind method is recommended. For usage of slice method please check tests.

How ClearX works:

The initial step in ClearX is creating an application data structure and creating a ClearX data store instance. Data attached to this instance can be accessed & modified from anywhere in the application including UI components that share React UI component structure.

ClearX provides a way to slice parts of data from the store and use it. This slice can be plugged to UI components. The slice keeps itself in sync with the latest data from the data store. When data is changed, the slice will trigger the UI component's re-render process and calls the afterUpdate callback.

Data store provides an expressive API to modify the data in the store.

Philosophy:

UI represents complex business-related operations of an application using a visual layer. UI layer often goes through a lot of iterations and sometimes requires to refactor in an Agile development environment. Global State Management is the spine part of an application & deserve to be simple for the long run. If we keep it simple to understand for Developers, it helps to maintain the application performance and quality of the application.

Usage:

Install:
npm install clearx --save
Creating a Store:
import ClearX from `clearx`

// Application data structure.
let ApplicationData = {
  user: {
    signedIn: false
  },
  test: 'one',
  data: {
    files: {
      file1: {
        name: 'my file1.txt'
      }
    }
  },
  a: {
    b: "d",
    c: ["e", "f"]
  }
}

// Create clearx instance with the data
let appStore = new ClearX(ApplicationData)

export default appStore
Slicing and Attaching the data store to a UI Component
import React, { Component } from 'react'

// Import application store
import appStore from './src/storeFile.js'

// It can be a class or a function. ClearX works with all.
class MyView extends Component {
  constructor (props) {
    super(props)
    // UI Component can have a local state. Optional step!
    this.state = {
      localState1: 'test'
    }
    appStore.bind({
      paths: {
        // Non existent keys can be used. ClearX automatically updates components when data is available.
        // Array format over 'data.files.file1' guards against unexpected . in keypath
        fileOne: ['data', 'files', 'file1']
      },
      to: this,
      // Optional. Sets the default data for above paths.
      withDefaultData: {
        fileOne: {
          name: 'FileOne.txt'
        }
      },
      // Optional events
      events: {
        // Called after data is updated.
        afterUpdate: this.afterUpdate.bind(this)
      },
      // Optional. Automatically detected for React family UI components
      isReactFamilyUIComponent: true
    })
    // Data bound is automatically destroyed.
    // Expected: { localState1: 'test', store: { fileOne: <data from path> }}
    console.log(this.state.store)
  }
  afterUpdate (data) {
    console.log('State updated', data)
  }
  render () {
    // Note: Always `this.state.store`
    let { fileOne } = this.state.store
    return (
      <div>{fileOne.name}</div>
    )
  }
}
Slicing and attaching the data store to a plain class
import appStore from './src/storeFile.js'

class Account {
  constructor () {
    this.sliced = appStore.bind({
      paths: {
        signedIn: ['user', 'signedIn']
      },
      to: this,
      events: {
        afterUpdate: this.checkData.bind(this)
      }
    })
    // If not react component, the location for sliced data is `this.store`
    console.log(this.store)
  }
  checkData () {
    if (this.store.signedIn) {
      // Fetch some initial data
    }
  }
  destroy () {
    // Need manual destroy for non UI components
    this.sliced.destroy()
  }
}

export default Account
Slicing and attaching the data store to a plain JavaScript Object
import appStore from './src/storeFile.js'

let data = {}

appStore.bind({
  paths: {
    fileTwo: ['data', 'files', 'file2']
  },
  to: this,
  events: {
    afterUpdate: checkData
  }
})

let checkData = () => {
   // If not react component, the location is `this.store`
   if (data.store.signedIn) {
     // Fetch some initial data
   }
}
Accessing & updating the application store without using slice.
// Import appStore from file.
import appStore from './src/storeFile.js'

class MyCls {
  constructor () {
    this.updateData()
  }
  updateData () {
    appStore.set(["a", "b"], "test")
    this.checkData()
  }
  checkData () {
    appStore.get(["a", "b"]) // Will be equal to "test"
  }
}

export default MyCls
Data reversal example

Data revert example:

import appStore from './src/storeFile.js'

class MyCls {
  constructor () {
    this.sliceInstance = appStore.bind({
      paths: {
        fileTwo_Name: ['data', 'files', 'file2', 'name']
      },
      to: this
    })
    this.postName()
  }
  checkData () {
    let instance = this.sliceInstance
    let old = instance.clone
    instance.slice = {
      fileTwo_Name: 'new name.txt'
    }
    makeSomeCall().then(() => {
      // all good
    }, () => {
      // ClearX will internally check for changes and updates other slices if there are any changes.
      instance.slice = old
    })
  }
}

export default MyCls

Example Todo implementation using React and ClearX

Please run:

  $ npm install
  $ npm install -g rollup
  $ npm run todo 

Now go to http://localhost:8719/ to access todo application. The source is available under ./example folder.

API:

The ClearX instance has below API:

bind:

Get a slice of data from the store & attach.

let sliceInstance = appStore.bind({
  // Key map
  paths: {
    fileOne: ['data', 'files', 'file1']
  },
  // Context
  to: this,
  // Default data to set in data store
  withDefaultData: {
    fileOne: 'my default filename.txt'
  },
  // Optional events
  events: {
    afterUpdate: function(data) {
      console.log('After store data is updated: ', data)
    }
  },
  // Optional. Automatically detected for React like components
  isReactFamilyUIComponent: true
})
console.log(sliceInstance.slice) // Data slice
sliceInstance.destroy() // Destroy slice
console.log(sliceInstance.clone) // Data slice clone
sliceInstance.slice = {} // Compares and replace the data in data store

slice (obsolete. Use bind):

Get a slice of data from the store & attach map:
Keys map. The path must be an array of keys. In above example 'data', 'files', 'file1' is equal to data.files.file1 but ClearX promotes array format to avoid conflicting with . in keys in data.

context:
The instance where the returned slice is bound to.

config:
Optional config can contain, defaults object, updateCallback function, reactLike boolean value.

// we do not need to store the sliceInstance if we don't have to call any methods on slice instance
let sliceInstance = appStore.slice(
  /* Keys map */
  {
    fileOne: ['data', 'files', 'file1']
  }, 
  /* context */ 
  this, 
  /* config */ 
  {
      // Default data to set in data store
      defaults: {
        fileOne: 'my default filename.txt'
      },
      // After data update event.
      updateCallback: {}
  }
)

console.log(sliceInstance.slice) // Data slice
sliceInstance.destroy() // Destroy slice
console.log(sliceInstance.clone) // Data slice clone
sliceInstance.slice = {} // Compares and replace the data in data store

destroy:

Destroy the store.

appStore.destroy()

localStores:

Get list of slices initialized.

appStore.localStores

data:

Get the root store data.

appStore.data

Remaining APIs below are adapted from object-path. It is an excellent library for operating on data. We can also check the documentation at this location. Except that we will not pass the data and key paths are always in key format. Methods from object-path are wrapped and exposed through store instance to trigger root store updates on data changes. merge method uses deepmerge

get:

Get deep property from root store. Takes key path as an input.

// Provide key to look for.
appStore.get(["data", "files", "fileOne"])

// works also with arrays
appStore.get(["a", "c", "1"])

// Can return a default value with get. Note: Does not update the root store with default value
appStore.get(["a", "c", "b"], "DEFAULT")  // returns "DEFAULT", since ["a", "c", "b"] path doesn't exists, if omitted, returns undefined

set:

Set a value at given path. Triggers root store updates if new value is different from old value.

appStore.set(["a", "h"], "m")
appStore.get(["a", "h"])  //returns "m"
// set will create intermediate object/arrays
appStore.set(["a", "j", 0, "f"], "m")

empty:

empty a given path (but do not delete it) depending on their type, so it retains a reference to objects and arrays. functions that are not inherited from the prototype are set to null. object instances are considered objects and just own property names are deleted. Triggers root store updates

appStore.empty(['a', 'b']) // ['a', 'b'] is now ''
appStore.empty(['a', 'c']) // ['a', 'c'] is now []
appStore.empty(['a']) // ['a'] is now {}

del:

Deletes a path. Also, Works on arrays. Triggers root store updates

appStore.del(["a", "b"]) // Root store value at ["a", "b"] is now undefined
appStore.del(["a", "c", 0]) // Root store value at ["a", "c"]is now ['f']

insert:

Insert values in array. Triggers root store updates

appStore.insert(["a", "c"], "m", 1) // Data at ["a", "c"] will be ["e", "m", "f"]

push:

Push into arrays (and create intermediate objects/arrays). Triggers root store updates

appStore.push(["a", "k"], "o")

ensureExists:

Ensure a path exists (if it doesn't, set the default value we provide). Triggers root store updates

appStore.ensureExists(["a", "k", "1"], "DEFAULT")
let oldVal = appStore.ensureExists(["a", "b"], "DEFAULT") // oldval === "d"

has:

Tests path existence

appStore.has(["a", "b"]) // true
appStore.has(["a", "d"]) // false

merge

Merges the data key with new data. Uses deepmerge library. We can pass in same options

appStore.merge(["a", "b"], {key: value}, {})

coalesce:

Get the first non-undefined value from list of keys provided.

appStore.coalesce(obj, [ ["data", "files", "fileOne"], ["data", "files", "fileTwo"]], {name: 'default filename.txt'})

Compatibility:

During development ClearX was tested with React and plain classes. ClearX will work with libraries like Preact, Inferno with no extra configuration considering they provide setState method and componentWillUnmount hook. To use ClearX with other UI libraries, please add a setState function on the component to receive the updated data. Also, call destroy() method before the UI component is unmounted.

Contributing:

Please check guidelines for more details.

2.1.1

4 years ago

2.1.0

4 years ago

2.0.9

4 years ago

2.0.8

4 years ago

2.0.7

4 years ago

2.0.6

5 years ago

2.0.5

5 years ago

2.0.4

5 years ago

2.0.3

5 years ago

2.0.2

5 years ago

2.0.1

5 years ago

2.0.0

5 years ago

1.0.5

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago