Parcel

Parcel is a data container. It’s job is to hold your data, split it into smaller parts, and merge changes back together.

If you’re using React, you probably won’t be instanciating parcels directly. Please see the getting started page to see how to best use Parcels in a React app.

import Parcel from 'dataparcels';
import Parcel from 'react-dataparcels';
new Parcel({
    value?: any,
    handleChange?: Function,
    // debugging options
    debugRender?: boolean
});
  • value ?: any = undefined

    The value you want to put in the Parcel. This value will be changed immutably when change methods are called on the Parcel. The data type of the value will determine the type of Parcel that will be created, and will determine which methods you can use to change the value. Please read Parcel types for more info.

  • handleChange ?: (newParcel: Parcel, changeRequest: ChangeRequest) => void

    The handleChange function will be called whenever the Parcel’s value has been triggered to change. It is passed newParcel, a replacement Parcel containing the changes; and changeRequest, a ChangeRequest that contains details about the change itself.

    In handleChange you would typically implement logic to replace your current parcel with newParcel, but if you’re using React you should read getting started to save you the trouble of implementing this yourself.

  • debugRender ?: boolean = false

    For debugging purposes. When set to true this causes all downstream ParcelBoundarys to display when they are being rendered and re-rendered.

// creates a Parcel that contains a value of 123
let parcel = new Parcel({
    value: 123,
    handleChange: (newParcel) => {
        // here you can insert logic to replace
        // your existing parcel with the newParcel
    })
});

Properties

value

value: any

Returns the Parcel’s value.

let value = 123;
let parcel = new Parcel({value});
parcel.value; // returns 123

meta

meta: Object

Returns an object containing the parcel’s meta data.

Please refer to Parcel Meta for more info on how to use meta.
let value = 123;
let parcel = new Parcel({value});
parcel.meta; // returns {}

// set initial meta and check again
parcel
    .initialMeta({abc: 123})
    .meta; // returns {abc: 123}

data

data: Object

Returns an object containing the Parcel’s data, which includes:

  • value - The Parcel’s value
  • meta - The Parcel’s meta object
  • key - The Parcel’s key
  • child - The Parcel’s child information, which includes any meta, key and child data related to the values children.
let value = 123;
let parcel = new Parcel({value});
parcel.data;

// return {
//     child: undefined,
//     key: '^',
//     meta: {},
//     value: 123,
// }

key

key: string

Returns the Parcel’s key. Parcels automatically gives unique keys to all children of a parent parcel. See parcel keys for more info.

Because they are unique, the can be used as keys when rendering an array of elements with React. This is demonstrated in the Editing Arrays example.

let value = {
    abc: 123,
    def: 456
};
let parcel = new Parcel({value});
parcel.get("abc").key; // returns "abc"
let value = [
    123,
    456
];
let parcel = new Parcel({value});
parcel.get(0).key; // returns "#a"

id

id: string

Returns the Parcel’s id. Under most circumstances, ids are unique among all Parcels that are descendants of a single original Parcel. You won’t often need to use this, but it can sometimes be useful for debugging. See parcel keypaths, locations and ids for more info.

let value = {
    abc: 123,
    def: 456
};
let parcel = new Parcel({value});
parcel.get("abc").id; // returns "^.abc"

path

path: string[]

Returns the Parcel’s path, an array of strings indicating how to access the current Parcel’s value.

let value = {
    abc: {
        def: 123
    }
};
let parcel = new Parcel({value});
parcel.get("abc").get("def").path; // returns ["abc", "def"]

Spread methods

spread()

spread(): {value: *, onChange: Function}
spread(notFoundValue: any): {value: *, onChange: Function}

Returns an object with the Parcel’s value and its onChange function.

If notFoundValue is provided, and the Parcel’s value is undefined or has been marked for deletion, the returned value will be equal to notFoundValue.

let parcel = new Parcel({
    value: 123
});

<MyInputComponent {...parcel.spread()} />

// ^ this is equivalent to
// <MyInputComponent value={parcel.value} onChange={parcel.onChange} />

spreadDOM()

spreadDOM(): {value: *, onChange: Function}
spreadDOM(notFoundValue: any): {value: *, onChange: Function}

Returns an object with the Parcel’s value and its onChangeDOM function.

If notFoundValue is provided, and the Parcel’s value is undefined or has been marked for deletion, the returned value will be equal to notFoundValue.

let parcel = new Parcel({
    value: 123
});

<input {...parcel.spreadDOM()} />

// ^ this is equivalent to
// <input value={parcel.value} onChange={parcel.onChangeDOM} />

Branch methods

get()

get(key: string|number): Parcel // only on ParentParcels
get(key: string|number, notSetValue: any): Parcel // only on ParentParcels

Returns a Parcel containing the value associated with the provided key / index. If the key / index doesn’t exist, a Parcel with a value of notSetValue will be returned. If notSetValue is not provided then a Parcel with a value of undefined will be returned.

let value = {
    abc: 123,
    def: 456
};
let parcel = new Parcel({value});
parcel.get('abc').value; // returns 123
parcel.get('xyz').value; // returns undefined
parcel.get('xyz', 789).value; // returns 789

get() with indexed values

When called on a Parcel with an indexed value, such as an array, get() can accept an index or a key.

  • index (number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.
  • key (string) is used to get a specific value by its unique key within the Parcel.
Parcels automatically gives unique keys to all elements of an indexed parcel. See parcel keys for more info.
let value = ['abc', 'def', 'ghi'];
let parcel = new Parcel({value});
parcel.get(0).value; // returns 'abc'
parcel.get(-1).value; // returns 'ghi'
parcel.get('#a').value; // returns 'abc'

getIn()

getIn(keyPath: Array<string|number>): Parcel // only on ParentParcels
getIn(keyPath: Array<string|number>, notSetValue: any): Parcel // only on ParentParcels

Returns a Parcel containing the value associated with the provided key path. If the key path doesn’t exist, a Parcel with a value of notSetValue will be returned. If notSetValue is not provided then a Parcel with a value of undefined will be returned.

let value = {
    a: {
        b: 123
    }
};
let parcel = new Parcel({value});
parcel.get(['a','b']).value; // returns 123
parcel.get(['a','z']).value; // returns undefined
parcel.get(['a','z'], 789).value; // returns 789

getIn() with indexed values

toObject()

toObject(mapper?: Function): Object // only on ParentParcels

toArray()

toArray(mapper?: Function): Parcel[] // only on ParentParcels

Parent methods

has()

has(key: string|number): boolean // only on ParentParcels

Returns true if the parcel has a child at the provided key or index, or false otherwise.

size()

size(): number // only on ParentParcels

Returns the number of children this parcel has.

Child methods

isFirst()

isFirst(): boolean

Returns true if this parcel is the first of its siblings.

isLast()

isLast(): boolean

Returns true if this parcel is the last of its siblings.

Change methods

onChange()

onChange(value: *): void

This is designed for use with input components that call onChange with a new value. It triggers a change that replaces the current value in the Parcel with the value provided.

If called on a ParentParcel, any child information that the Parcel had is removed, such as child keys or meta.

It is equivalent to calling set with no key.

let parcel = new Parcel({
    value: 123
});

<MyInputComponent
    value={parcel.value}
    onChange={parcel.onChange}
/>

See also:
- onChangeDOM for use with HTML inputs
- spread for convenient spreading of value and onChange onto an input

onChangeDOM()

onChangeDOM(event: Event): void

This is designed for use with HTML inputs. It triggers a change that replaces the current value in the Parcel with the value of the event provided.

If called on a ParentParcel, any child information that the Parcel had is removed, such as child keys or meta.

let parcel = new Parcel({
    value: 123
});

<input
    value={parcel.value}
    onChangeDOM={parcel.onChangeDOM}
/>

See also:
- onChangeDOM for use with input components that call `onChange` with a new value.
- spreadDOM for convenient spreading of value and onChange onto an input

set()

set(value: *): void
set(key: string|number, value: *): void // only on ParentParcels, will set a child

setIn()

setIn(keyPath: Array<string|number>, value: *): void // only on ParentParcels

update()

update(updater: Function): void
update(key: string|number, updater: Function): void // only on ParentParcels, will set a child

updateIn()

updateIn(keyPath: Array<string|number>, updater: Function): void // only on ParentParcels

delete()

delete(): void
delete(key: string|number): void // only on ParentParcels, will delete a child

deleteIn()

deleteIn(keyPath: Array<string|number>): void // only on ParentParcels

Indexed & element change methods

insertAfter()

insertAfter(value: *): void // only on ElementParcels, will insert after self
insertAfter(key: string|number, value: *): void // only on IndexedParcels, will insert after child

insertBefore()

insertBefore(value: *): void // only on ElementParcels, will insert before self
insertBefore(key: string|number, value: *): void // only on IndexedParcels, will insert before child

push()

push(value: *): void // only on IndexedParcels

pop()

pop(): void // only on IndexedParcels

shift()

shift(): void // only on IndexedParcels

swap()

swap(key: string|number): void // only on ElementParcels, will swap with sibling
swap(keyA: string|number, keyB: string|number): void // only on IndexedParcels, will swap children

swapNext()

swapNext(): void // only on ElementParcels, will swap with next sibling
swapNext(key: string|number): void // only on IndexedParcels, will swap child with next child

swapPrev()

swapPrev(): void // only on ElementParcels, will swap with previous sibling
swapPrev(key: string|number): void // only on IndexedParcels, will swap child with previous child 

unshift()

unshift(value: *): void // only on IndexedParcels

Advanced change methods

setMeta()

setMeta(partialMeta: Object): void

updateMeta()

updateMeta(updater: Function): void

setChangeRequestMeta()

setChangeRequestMeta(meta: Object): void

dispatch()

dispatch(dispatchable: Action|Action[]|ChangeRequest): void

batch()

batch(batcher: Function): void

batchAndReturn()

batchAndReturn(batcher: Function): ?Parcel // only on TopLevelParcels

ping()

ping(): void

Modify methods

modifyValue()

modifyValue(updater: Function): void

modifyChangeBatch()

modifyChangeBatch(batcher: Function): void

modifyChangeValue()

modifyChangeValue(updater: Function): void

initialMeta()

initialMeta(initialMeta: Object): void

Type methods

isChild()

isChild(): boolean

Returns true if the parcel is a child parcel. Read Parcel types for more info.

When a parcel is a child parcel, it allows the use of child methods.

isElement()

isElement(): boolean

Returns true if the parcel is an element parcel. Read Parcel types for more info.

When a parcel is an element parcel, it allows the use of element methods.

isIndexed()

isIndexed(): boolean

Returns true if the parcel is an indexed parcel. Read Parcel types for more info.

When a parcel is an indexed parcel, it allows the use of indexed methods.

isParent()

isParent(): boolean

Returns true if the parcel is a parent parcel. Read Parcel types for more info.

When a parcel is a parent parcel, it allows the use of branch methods and parent methods.

isTopLevel()

isTopLevel(): boolean

Returns true if the parcel is a top level parcel. Read Parcel types for more info.

Status methods

hasDispatched()

hasDispatched(): boolean

Side-effect methods

spy()

spy(sideEffect: Function): Parcel

When the spy method is called on a parcel, it immediately calls the sideEffect function, passing itself as the first parameter. The return value of sideEffect is ignored. It returns the original parcel, so it can be chained. This is useful for debugging.

let value = {
    abc: 123
};
let parcel = new Parcel({value});
parcel
    .spy(parcel => parcel.toConsole()) // 1. logs the parcel to the console ({abc: 123})
    .get('abc')
    .spy(parcel => parcel.toConsole()) // 2. logs the parcel to the console (123)
    .value; // 3. returns 123

spyChange()

spyChange(sideEffect: Function): Parcel

Once the spyChange method is called on a parcel, it will call the sideEffect function each time a change is requested from beneath, passing the associated ChangeRequest as the first parameter. The return value of sideEffect is ignored. It returns a clone of the original parcel, so it can be chained. This is useful for debugging.

let value = {
    abc: 123
};
let parcel = new Parcel({value});
parcel
    .spyChange(changeRequest => changeRequest.toConsole()) // 3. logs the change request to the console (containing {abc: 456})
    .get('abc')
    .spyChange(changeRequest => changeRequest.toConsole()) // 2. logs the change request to the console (containing 456)
    .onCHange(456); // 1. a change is made

Composition methods

pipe()

pipe(...updaters: Function[]): Parcel

The pipe method allows for a parcel to be passed through one or more parcel modifying functions, while retaining the ability to chain. It allows for easier function composition.

let valueToString = (parcel) => parcel.modifyValue(value => `${value}`);
let changeToNumber = (parcel) => parcel.modifyChangeValue(value => Number(value));

let parcel = new Parcel({value: 123});
parcel
    .pipe(
        valueToString,
        changeToNumber
    )
    .value // returns "123"

The above is equivalent to:

let parcel = new Parcel({value: 123});
parcel
    .modifyValue(value => `${value}`)
    .modifyChangeValue(value => Number(value))
    .value // returns "123"

matchPipe()

matchPipe(match: string, ...updaters: Function[]): Parcel

Debug methods

log()

log(name: string?): Parcel

Output’s the parcel’s data to the console each time data flows downward, and outputs the ChangeRequest each time a change has propagated upward.

toConsole()

toConsole(): void

Outputs the parcel’s data to the console.