omniaural
v1.6.1
Published
A lightweight global state manager
Downloads
31
Readme
OmniAural
OmniAural is a minimal global state manager for React/React-Native applications.
Core Concepts
OmniAural provides global state managment with no need for boiler plate code.
This is done by allowing React components to 'register' for the global state elements they need to be aware of. Those global elements then become part of the component's local state and can be treated like, read-only, local state from the component's point of view.
Changing global state is handled through a call to OmniAural that can be called from any js code (not just components). However, the recommened pattern is to put global state changes in OmniAural actions which are a convenient way to orginize these global state changes. Actions are optional and may not be best suited for all types of projects, but are recommended for most.
Getting started
Instalation
Install package from npm
yarn add omniaural
Initialize
In your top level component (usually App.js) import OmniAural and initialize the global state
import OmniAural from 'omniaural';
OmniAural.initGlobalState({
account: {
name: 'Jack',
phone: '3129058787',
address: {
"street": "1st st"
}
},
movies: []
})
Usage
After initialization, global state properties can be (but rarely need to be) accessed directly through the .value()
function. See below (Register a component) on the recommened approach to accessing global state values.
OmniAural.state.account.name.value()
Global state properties can be set directly through the .set()
function.
OmniAural.state.account.name.set("John")
Global state properties can also be deleted directly through the .delete()
function.
(This function should be used with care. It can cause UI issues if you are not handling property existence checking in your components)
OmniAural.state.account.name.delete()
Register a component
You can register to listen to a particular property or a whole object on the global state. You can also use aliases to allow for local naming that makes more sense for your component. IMPORTANT - if you create a state object in your component, this must be done before you call OmniAural.register.
import React from 'react'
import OmniAural from 'omniaural'
export class IntroScreen extends React.Component<*, *> {
constructor() {
super()
this.state = {
person: {
employed: true
}
}
// Register for the global 'account' (defaults to 'account' in local state)
// Register for the account.address as 'address' in local state
OmniAural.register(this, ['account', 'account.address as address'])
}
render() {
return (
<div style={styles.container}>
<span style={styles.instructions}>
Account information
</span>
<div style={styles.instructions}>
{'\n'}
{`Name: ${this.state.account.name}` /* I'm accessing global state here, it just looks local to the component */}
{'\n'}
{`Currently employed: ${this.state.person.employed}` /* this is actually local state */}
{'\n'}
{`Street: ${this.state.address.street}` /* this is global state again */}
</div>
</div>
)
}
}
const styles = {
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white'
}
}
Update global state
Using the OmniAural.state object, you can make changes to the global state values.
import React from 'react'
import OmniAural from 'omniaural'
export class IntroScreen extends React.Component<*, *> {
constructor() {
super()
this.state = {
person: {
employed: true
}
}
OmniAural.register(this, ['account', 'account.address as address'])
}
_updateAddress = () => {
// Updating the street to a hard coded "Main st"
// Note this is the full global state path
OmniAural.state.account.address.street.set("Main st")
}
render() {
return (
<div style={styles.container}>
<span style={styles.instructions}>
Account information
</span>
<div style={styles.instructions} onClick={this._updateAddress}>
{'\n'}
{`Name: ${this.state.account.name}`}
{'\n'}
{`Currently employed: ${this.state.person.employed}`}
{'\n'}
{`Street: ${this.state.address.street}`}
</div>
</div>
)
}
}
const styles = {
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white'
}
}
Adding an action
Actions are the prefered way to encapsulate your global state changes. They can be added from any file in the global space, although it makes sense to group these actions in designated files.
import React from 'react'
import OmniAural from 'omniaural'
// Add a globally accessable action to update the global address object
OmniAural.addAction('updateAddress', (address) => {
OmniAural.state.account.address.set(address)
})
export class IntroScreen extends React.Component<*, *> {
constructor() {
super()
this.state = {
person: {
employed: true
}
}
OmniAural.register(this, ['account.name', 'account.address as address'])
}
_updateAddress = () => {
// call the global action using the name passed into OmniAural.addAction
OmniAural.updateAddress({street: "Main st"})
}
render() {
return (
<div style={styles.container}>
<span style={styles.instructions}>
Account information
</span>
<div style={styles.instructions} onClick={this._updateAddress}>
{'\n'}
{`Name: ${this.state.account.name}`}
{'\n'}
{`Currently employed: ${this.state.person.employed}`}
{'\n'}
{`Street: ${this.state.address.street}`}
</div>
</div>
)
}
}
const styles = {
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white'
}
}
Functional components
You can register a functional component by creating an OmniAural state hook useOmniAural
import React from 'react'
import { useOmniAural } from 'omniaural'
const PersonScreen = () => {
const [person, setPerson] = useOmniAural("account")
return (
<div>
<span>User Id: {person.id}</span>
</div>
)
}
export default PersonScreen
You can register a functional component to listen to OmniAural state property updates using useOmniAuralEffect
import React from 'react'
import { useOmniAuralEffect } from 'omniaural'
const PersonScreen = () => {
const [person, setPerson] = useOmniAural("account")
useOmniAuralEffect(()=> {
console.log("The account id has changed")
}, "account.id")
return (
<div>
<span>User Id: {person.id}</span>
</div>
)
}
export default PersonScreen
You can also register a functional component by wrapping it in the withOmniAural HOC function
import React from 'react'
import { withOmniAural } from 'omniaural'
const PersonScreen = (props) => {
return (
<div>
<span>User Id: {props.person.id}</span>
</div>
)
}
export default withOmniAural(PersonScreen, ["account as person"])
API
OmniAural
The main global state manager class. It should be initialized at your top most component (usually App.js). This class will contain your global state. Components can register to specific properties or the whole global state object and update when it changes.
Methods:
- initGlobalState()
- register()
- addAction()
- addActions()
- addProperty()
- getProperty()
- setProperty()
- clearProperty()
- useOmniAural()
- useOmniAuralEffect()
- withOmniAural()
initGlobalState()
Initialize the global state with an initial object to which components will register and listen to its changes. You can access and update properties directly from the creates state object.
| Parameter | Type | Description | | ------------- |:------------: | :----------- | | initialState | Object | The object with which to initialize your global object. | config | Object | (Optional) Optional configuration object to set special properties on OmniAural initialization.
Config
| Parameter | Type | Description |
| ------------- |:------------: | :----------- |
| pathDelimiter | String | (Optional) If you plan on using object keys with periods .
i.e.(a url path), you will want to change the default delimiter to another character eg. \|
. Doing so will change string based path interpretation from needing "account.address.street"
to needing "account\|address\|street"
.
Example:
import { initGlobalState } from 'omniaural';
initGlobalState({
account: {
name: 'Jack',
phone: '3129058787',
address: {
"street": "1st st"
}
},
movies: []
})
console.log(OmniAural.state.account.phone.value()) //Prints 3129058787
OmniAural.state.account.phone.set('2125548844')
console.log(OmniAural.state.account.phone.value()) //Prints 2125548844
Can also be used directly on OmniAural:
import OmniAural from 'omniaural';
OmniAural.initGlobalState({
account: {
name: 'Jack',
phone: '3129058787',
address: {
"street": "1st st"
}
},
movies: []
})
register()
Register a component to listen to changes on the global state. This method will add the component as a listener to the passed in properties that you want to listen to and call setState
on that component whenever a change happens.
An alias can be used for each property using the keyword as
and will be added to the local state using that alias.
Note: This function must be called after you have initialized your initial local state (if any).
| Parameter | Type | Description | | ------------- |:---------------: | :----------- | | component | React.Component | The component to be registered as a listener to the global state properties. | paths | string | Array | The global state path(s) to subscribe to. It cab either be a string or an array of strings. If an empty array is passed in, the whole global state will be observed (not recommended). | listener | Function | null | (Optional) A function that will fire when one of values of the passed in paths changes.
Example:
import OmniAural from 'omniaural'
constructor() {
super()
this.state = {}
OmniAural.register(this, ['account as person', 'account.address as address'])
}
addAction()
Actions can be added to OmniAural to be used as batch updates or async calls that need to update the global state after they are completed. You can add an action as a predeclared named function or by passing an anonymous function and a name for it.
| Parameter | Type | Description | | ------------- |:----------------------: | :----------- | | action | Function | The body of this function will be added as a global action. Must be a named function
Example:
import OmniAural from 'omniaural'
const updateAddress = (address) => {
OmniAural.state.account.address.set(address)
}
OmniAural.addAction(updateAddress)
_onClick = () => {
OmniAural.updateAddress({street: "Main st"})
}
* If you don't want to use a named function, you can also pass a string as the first argument that will represent the name of the function on OmniAural and an anonymous funtion as a second argument
| Parameter | Type | Description | | ------------- |:----------------------: | :----------- | | name | String | The name of the function that will be save on OmniAural | action | Function | The body of this function will be added as a global action.
Example:
import OmniAural from 'omniaural'
OmniAural.addAction('updateAddress', (address) => {
OmniAural.state.account.address.set(address)
})
_onClick = () => {
OmniAural.updateAddress({street: "Main st"})
}
addActions()
Actions can be added to OmniAural in bulk by passing named functions as arguments to this function
| Parameter | Type | Description | | ------------- |:----------------------: | :----------- | | actions | Object | An object with one or more named functions to be added to OmniAural. Note: All actions must be named functions
Example:
import OmniAural from 'omniaural'
const updateAddress = (address) => {
OmniAural.state.account.address.set(address)
}
const updateAccount = (account) => {
OmniAural.state.account.set(account)
}
OmniAural.addActions({updateAccount, updateAddress})
_onClick = () => {
OmniAural.updateAddress({street: "Main st"})
}
_onAnotherClick = () => {
OmniAural.updateAccount({address: street: "Main st"}})
}
addProperty()
This function adds new properties to the global state object structure. If you add properties to nested objects, any listener to the parent object will also start listenting to the newly added property.
| Parameter | Type | Description | | ------------- |:------------: | :----------- | | path | String | The path in the global state to which to add a property. | value | any | The value to initialize the newly added property as.
Example:
import OmniAural from 'omniaural'
OmniAural.addProperty("account.id", 4568585)
//or
OmniAural.addProperty("account", {id: 4568585})
getProperty()
This functions accepts a path to a property and returns an object which you can use to get the global value.
| Parameter | Type | Description | | ------------- |:------------: | :----------- | | path | String | The path in the global state to the property to get.
Example:
import OmniAural from 'omniaural'
const accountId = OmniAural.getProperty("account.id")
console.log(accountId.value()) // 4568585
setProperty()
This function updates a property at a given path. It receives a string representing the path to the property and a value to update the property with.
| Parameter | Type | Description | | ------------- |:------------: | :----------- | | path | String | The path in the global state to the property to update. | value | any | The value to set the passed in property with.
Example:
import OmniAural from 'omniaural'
OmniAural.setProperty("account.id", 4568585)
//or
OmniAural.setProperty("account", {id: 4568585})
clearProperty()
This function empties an object property at a given path. It receives a string representing the path to the property
| Parameter | Type | Description | | ------------- |:------------: | :----------- | | path | String | The path in the global state to the property to update.
Example:
import OmniAural from 'omniaural'
OmniAural.clearProperty("account.address")
HOOKS
useOmniAural
The useOmniAural
hook is a custom hook that creates a local variable tied to a global state property value. It can be used in stateless
functional components to register to variables that live on global state.
| Parameter | Type | Description | | ------------- |:------------: | :----------- | | path | String | A string path that represent the path to the global properties to register to.
Example:
import React from 'react'
import { useOmniAural } from 'omniaural'
const PersonScreen = () => {
const [accountId, setAccountId] = useOmniAural("account.id")
return (
<div>
<span>User Id: {accountId}</span>
</div>
)
}
export default PersonScreen
useOmniAuralEffect
The useOmniAuralEffect
hook takes a function and a path (or an array of paths) to properties on the OmniAural state and fires the
passed in function when those properties values change.
| Parameter | Type | Description | | ------------- |:------------: | :----------- | | listener | Function | A function that will be called when any of the passed in property paths values change | path(s) | String or Array | A string (or array of strings) that represent the path to the global property to register the listener to.
Example:
import React from 'react'
import { useOmniAuralEffect, useOmniAural } from 'omniaural'
const PersonScreen = () => {
const [accountId, setAccountId] = useOmniAural("account.id")
useOmniAuralEffect(() => {
console.log("Account Id has changed")
}, ["account.id"])
return (
<div>
<span>User Id: {accountId}</span>
<div>
<span>Update Account Id</span>
<button onClick={() => setAccountId(321321321)}>Update</button>
</div>
</div>
)
}
export default PersonScreen
HOC
withOmniAural()
This function can be used to register a functional component with a collection of properties from the global state using a higher order component. The registered properties will be passed in as props to the functional component.
| Parameter | Type | Description | | ------------- |:------------: | :----------- | | component | Function | The functional component to start listening to the global state. | paths | Array | An array of strings that represent the paths to the global properties to listen to. Each path can receive an alias similar to the register function to be passed into the props.
Example:
import React from 'react'
import { withOmniAural } from 'omniaural'
const PersonScreen = (props) => {
return (
<div>
<span>User Id: {props.person.id}</span>
</div>
)
}
export default withOmniAural(PersonScreen, ["account as person"])
Troubleshooting & Gotchas
Most of the errors that OmniAural can throw come from incorrectly listening to nested properties. If you are experiencing a crash referring to accessing property of null
, or something similar, you are most likely listening to a property of an object that was either initialized to null or was set to null during some part of execution.
If you know an object can become null, avoid listening to its properties. Rather, listen to the object itself. This way you can code against attempting to access a property of a null object.
Example:
const initialState = {
account: {
address: null
},
movies: []
}
=========================================
import React from 'react'
import { useOmniAural } from 'omniaural'
const PersonScreen = () => {
//BAD Because you are listening to a property of a potentially null object and this will crash if address is null
const [streetAddress, setStreetAddress] = useOmniAural("account.address.street")
//GOOD Because you can code against *address* being null, either on initialization or set to null later
const [address, setAddress] = useOmniAural("account.address")
const streetAddress = address !== null ? address.street : "Not found"
return (
<div>
<span>Street Address: {streetAddress}</span>
</div>
)
}
export default PersonScreen
License
MIT License
Copyright (c) 2020 Isobar North America, Inc. (Chris Steele and Creon Creonopoulos)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.