The useParcelState function is a React hook. Its job is to provide you with a Parcel stored in React state, and to handle how the parcel responds to changes in React props.
By default, useParcelState operates in a similar way to an uncontrolled component, accepting an initial value once on first mount, and then becoming the master of its own state from then on. This behaviour can be changed using the updateValue parameter.
import useParcelState from 'react-dataparcels/useParcelState';
let [parcel] = useParcelState({
value: any,
// optional
updateValue?: boolean,
rebase?: boolean,
onChange?: Function,
onChangeUseResult?: boolean,
beforeChange?: Function|Function[]
});
value: any
Sets the initial value to be put into useParcelState's Parcel.
let [parcel] = useParcelState({
value: 100
});
// parcel.value is 100
parcel.set(200);
// set() triggers a change and a re-render
// parcel.value is now 200
If computing value
is a heavy operation, you can return the value from a function. The function will only be called on initial mount, and is passed the previous value. However, if updateValue
is set to true then the function will be called on every update.
let newValue = 100;
let [parcel] = useParcelState({
value: (prevValue) => newValue
});
// parcel.value is 100
Value can also accept parcel updaters. These pass the previous data held in state, and expect the new data to be returned. These can be useful for setting parcel meta.
import asNode from 'react-dataparcels/asNode';
let [parcel] = useParcelState({
value: asNode(node => node
.update(() => newValue)
.setMeta({
foo: true
})
)
});
import asyncValue from 'react-dataparcels/asyncValue';
let [parcel] = useParcelState({
value: asyncValue(async () => {
// add logic here
return theValue;
})
});
It's possible to return a value from a promise using the asyncValue
function. The useParcelState
hook's parcel has a value of undefined
until the promise resolves. Once it has resolved, useParcelState
hook's parcel has a value of the result of the async function.
When using asyncValue
you will also receive its status via valueStatus.
updateValue?: boolean = false // optional
When updateValue
is set to true during an update, the useParcelState hook will check to see if value
has changed, and will update its Parcel's value if so. This will completely replace any changes that may have happened to the Parcel since the last time value
was put into the Parcel.
Note that it will also cause any downstream useParcelBuffer hooks and ParcelBoundaries to forget all their buffered changes, unless rebase is used.
Value changes are detected using Object.is()
, comparing the new value
with the previous one.
// receivedValue is 100
let [parcel] = useParcelState({
value: receivedValue,
updateValue: true
});
// parcel.value is 100
parcel.set(200);
// set() triggers a change and a re-render
// parcel.value is now 200
// if component updates and receivedValue is now 300
// then parcel.value is now 300
rebase?: boolean = false // optional
As described above, updates caused by updateValue
will cause any downstream useParcelBuffer hooks and ParcelBoundaries to forget all their buffered changes. This is safe default behaviour because changes in the downstream buffers may not be compatible with the new Parcel's data shape. However it may be user unfriendly in some cases, depending on when and how often the parcel updates.
Setting rebase
to true will prevent downstream buffers from being cleared.
Only use this if the shape of your data does not change, so that downstream buffered changes are compatible with the new Parcel's data shape.
This restriction will be lifted in future with the introduction of a feature known as rekey.
onChange?: (parcel: Parcel, changeRequest: ChangeRequest) => any // optional
If provided, this function is called whenever useParcelState's Parcel has handled a change. It receives the new Parcel, and the ChangeRequest that was responsible for the change.
This function can be used to relay changes further up the React heirarchy.
Please keep in mind that it is possible for a change to result in the same data being contained in the Parcel, onChange
will not dedupe subsequent calls whose Parcels contain the same data. ChangeRequest.hasValueChanged() can help with that.
let [parcel] = useParcelState({
value,
onChange: (parcel, changeRequest) => {
// add logic here
}
});
import asyncChange from 'react-dataparcels/asyncChange';
let [parcel] = useParcelState({
value,
onChange: asyncChange(async (parcel, changeRequest) => {
// add logic here
})
});
It's possible to return a promise from onChange
using the asyncChange
function. When doing this, the change does not enter the hook's state until the promise resolves.
If another change arrives while a promise is still pending, it will be passed through onChange
after the first promise is resolved or rejected. This is to ensure that there is only one operation happening at a time. If the first ChangeRequest's
promise is rejected, the changes will be merged with the next ChangeRequest when onChange
is called the second time.
This is discussed in more detail in data synchronisation.
When using asyncChange
you will also receive its status via changeStatus.
onChangeUseResult?: boolean = false // optional
When true, this sets the value of the hook's parcel to the return value of onChange
.
let [parcel] = useParcelState({
value: receivedValue,
onChange: (parcel, changeRequest) => {
return saveMyData(parcel.value);
// ^ saveMyData send a request to a server to save the data,
// and returns a promise containing the updated data from the server
},
onChangeUseResult: true
});
Note that it will also cause any downstream useParcelBuffer hooks and ParcelBoundaries to forget all their buffered changes, unless rebase is used.
beforeChange?: ParcelUpdater|ParcelUpdater[] // optional
type ParcelUpdater = (value: any, changeRequest: ChangeRequest) => any
type ParcelUpdater = asNode((node: ParcelNode, changeRequest: ChangeRequest) => ParcelNode);
type ParcelUpdater = asChildNodes((nodes: any, changeRequest: ChangeRequest) => any);
The beforeChange
parameter accepts either a single function, or an array of functions. Whenever a new value
is taken into useParcelState from params, and whenever the useParcelState hook recieves a change from below, the change is passed through each beforeChange
function.
Internally the useParcelBuffer hook uses Parcel.modifyUp() on each of the beforeChange
functions. If more than one function is passed to beforeChange
, the change will go through the first function in the array first, then the second etc.
This is particularly useful for setting derived data, and plugins such as validation are built to be passed into beforeChange
.
This method is safe to use in most simple cases, but in some cases it should not be used:
To find out why, and what to do about it, please read about parcel updaters.
let [parcel] = useParcelState({
value: "ABC",
beforeChange: value => value.toLowerCase()
});
// ^ "ABC" will be passed through `beforeChange`
// and useParcelState's Parcel will contain a value of "abc"
// parcel.value is now "abc"
parcel.set("HELLO");
// ^ "HELLO" will be passed through `beforeChange`
// and useParcelState's Parcel will contain a value of "hello"
// parcel.value is now "hello"
[parcel: Parcel, parcelControl: ParcelHookControl]
parcel: Parcel
The first element of the returned array is the parcel that this hook holds in React state.
parcelControl: ParcelHookControl
The second element of the returned array is an object containing data and functions for controlling the hook.
type ParcelHookControl {
valueStatus?: Object,
changeStatus?: Object
}
Status is always one of three possible string values:
"pending"
- if asyncValue
s promise is pending."resolved"
- if the last promise returned from asyncValue
was resolved."rejected"
- if the last promise returned from asyncValue
was rejected.The isPending
boolean is true if asyncValue
s promise is pending, otherwise it is false.
The isResolved
boolean is true if the last promise returned from asyncValue
was resolved.
The isRejected
boolean is true if the last promise returned from asyncValue
was rejected.
If the last promise returned from asyncValue
was rejected, this contains the rejected promise's payload.
Status is always one of four possible string values:
"idle"
- no promises have yet been returned from asyncChange
"pending"
- if asyncChange
s promise is pending."resolved"
- if the last promise returned from asyncChange
was resolved."rejected"
- if the last promise returned from asyncChange
was rejected.The isPending
boolean is true if asyncChange
s promise is pending, otherwise it is false.
The isResolved
boolean is true if the last promise returned from asyncChange
was resolved.
The isRejected
boolean is true if the last promise returned from asyncChange
was rejected.
If the last promise returned from asyncChange
was rejected, this contains the rejected promise's payload.