Consume RjObjects
#
Action creators BuilderWhen you consume an RjObject its action creators are bound to current state and side effects instance. Plus a special builder were attached. You can still inoke them as simply functions.
The methods exposed on bound action creators by the builder are:
Each of these methods instantiates a new Builder to be used for the rich call, and calls the omonimous method on the created builder (so avoiding an explicit call to instantiate the builder).
The methods exposed by the builder object are:
First of all, builder methods are designed to be chainable, hence probably you'll never create a builder
variable in your code, but you'd end up writing something like
Now, let's go describing the methods of the builder.
#
withMeta(function)builder.withMeta(oldMeta => newMeta)
The withMeta
builder method allows to add a transform on metadata attached to the action. The transform is encoded as a function that receives the old metadata object (a JavaScript plain object) and is required to return the next metadata object (again, as a plain JavaScript object). This method can be used to add some metadata, to change some of them, or even to delete some keys (even if you should do it with care). Calling withMeta
multiple times simply chains all the transformation, such that the output of the first is the input of the second and so on, the metadata object attached to the action will be the output of the last transform.
Example
This call exposes the key
meta data under the id
property
#
withMeta(object)builder.withMeta(object)
This is equivalent to builder.withMeta(prevMeta => { ...prevMeta, ...object })
, it can be useful as a quick and lightweight way to add some keys to the metadata object
#
onSuccessbuilder.onSuccess(callback)
This method allows to attach a callback to be invoked when the asynchronous task completes without errors. The callback is not invoked in case the task it is attached to is canceled (see takeEffect). Calling this method multiple times on the same builder causes the callback to be overwritten: the callback attached to the action is the argument of the last invocation of this method
Example
Triggering an alert when an action completes
#
onFailurebuilder.onFailure(callback)
This method allows to attach a callback to be invoked when the asynchronous task completes with errors. The callback is not invoked in case the task it is attached to is canceled (remember takeEffect). Calling this method multiple times on the same builder causes the callback to be overwritten: the callback attached to the action to handle failures is the argument of the last invocation of this method
Example
Triggering an alert when an action completes
#
currybuilder.curry(...args)
This method return a new builder instance with args
curried.
The previous onSuccess
, onFailure
and withMeta
are also copied to the
new builder instance.
Cause the curry method return a new instance of builder is prefered to wrap it
in React.useMemo
.
The curry pattern can be useful if you need to pass to a child component a callback with the capabilities of builder with wrapped logic:
Example
#
runbuilder.run(...args)
This method closes the builder and dispatches the action with the passed in ...args
as params and the configuration defined with the other methods. This method must be the last invocation on a builder, no further configuration will be taken into account after calling it.
#
asPromisebuilder.asPromise(...args)
This method closes the builder and dispatches the action with the passed in ...args
as params and the configuration defined with the other methods. This method must be the last invocation on a builder, no further configuration will be taken into account after calling it. The return value of this method is no more a Builder
instance, but a Promise
.
If you set callbacks with builder.onSuccess
or builder.onFailure
, they will be invoked properly before the Promise completes (either resolving or rejecting)
Example
Returning a Promise from an action
#
useRjuseRj
is a React Hook that allows the instantiation of one RjObject
which is then made available to the component.
It accept an RjObject as input and return its computed state
and action creators.
useRj acept a second optional parameter selectState
:
You can use it to map the returned state using selectors:
#
connectRjThe connectRj
function give you the exact contract of useRj
but in form of
higher-order component (HOC).
It accept an RjObject and give you the computed state and action creators
as props:
You can change the mapping between your RjObject and your React Component using
two optional paramters provided by connectRj.
The mapStateToProps
argument:
And the mapActionsToProps
argument:
Let's an example:
#
The compose helperFinally RocketJump provide you a functional helper to compose togheter multiple
connectRj
instance.
Instead of writing this:
You can use the compose
helper and write:
#
useRunRjThe useRunRj
hook, just like useRj
, allows to instantiate a RjObject in the definition of a React Component. Unlike useRj
, this hook automatically schedules the run
call of the RjObject when there is a change in its arguments. To stress the concept that the control point of the effect are its arguments, we will also refer to them as dependencies, but this is only a different point of view: the dependencies argument of the useRunRj
hook is used to understand when the effect should be run, and the items of the dependencies array is spread as arguments on the run
call.
#
Basic usageThe signature of the hook is:
rjObject
: an RjObject.
deps
is the dependencies array. You can pass any JavaScript array with any content (provided you can deal with it in the implementation of the effect
property of the rjObject
argument), or you can decide to opt-in for automatic dependency management (see later). Either way, the items you put in this array will be the arguments with which your effect will be invoked.
shouldCleanBeforeRun
, if set to true
, instructs the hook to run the clean
action before triggering run
(except for the first run, when no clean is triggered)
mapStateToProps
is a function that is used to modify the shape of the state before returning it to the component.
Works as explained in useRj
section.
This is a very simple example of how this hook is expected to be used:
Dependencies should be defined with care to avoid unnecessary run
calls. The following example shows how you can blow your work up with a simple distraction
In this example, the first (and only) item of the dependencies array is a plain object. While this is not an issue for rocketjump itself, it causes an infinite loop of calls. This comes from the fact that, at each render, a new Object
instance is created from that definitions, and hence, when rocketjump checks if dependencies array changed, if finds a different object with respect to the previous render call, and so it triggers a new run
. When this run completes, the component is re-rendered with the resource
const set to the results, but in rerendering the object passed as a dependency changes again, hance the loop.
The proper way to achive the same behaviour without blowing up the application is to rely on memoization to ensure that dependencies array contains referentially stable items
#
Automatic dependency managementWhen using multiple RjObjects in the same component, it may happen that the dependency array of a useRunRj
hook depends on the response of the invocation of another useRunRj
. This is only a particular case of a more complex problem: how can we deal with potentially missing dependencies? Since hooks cannot be called conditionally, the only way to trigger a run
action on some RjObject only when its dependencies are available is to manually write a conditional call inside a custom useEffect
. Starting with react-rocketjump
version 2.1.0, dependency management can be solved in a declarative way by marking dependencies that must be awaited before a run is triggered. Note that the concept of awaiting is general: you may want to await that an API call completes as well as that the user fills out a box and hits Enter
.
RocketJump provides various tools to deal with (potentially) missing dependencies, which can be imported as
Each tool can be interpreted as a marker, which instructs the rocketjump engine whether to trigger a run
or not, and eventually to attach some metadata to it. There are two main classes of markers: run preventing markers and meta setting markers
#
deps.mayberun preventing marker
The maybe marker is the most simple marker you can think of: it tells the engine not to run the effect as long as the value
argument is falsy, and to run it inserting value
as a positional argument otherwise
The Dep
instance returned by the marker exposes a withMeta method, that allows to set some metadata to be attached to the next run
call in case value
has changed from the last run
call (metadata are always attached to the first call, since there is no previous value to compare).
For example, you can do something like this
value can belong to any JS type or it can be the output of a withMeta marker. Using any other markers result in undefined behaviour. Note that the following statements are fully equivalent:
Note that you can chain .withMeta()
invocations on the output of a marker. In this case, the final meta
object will be the shallow merging of the meta
objects in call order (i.e. the later the call the higher the priority)
#
deps.maybeNullrun preventing marker
This marker is very similar to deps.maybe
, but prevents a run
call only in case the value
argument is null
, and not any falsy value like the deps.maybe
. So, please refer to the docs about deps.maybe
keeping this difference in mind.
#
deps.maybeGetrun preventing marker
This marker is very similar to deps.maybe
, but instead of injecting value
as an argument to the run
call, it injects lodash.get(value, path)
. So, please refer to the docs about deps.maybe
keeping this difference in mind
#
deps.allMayberun preventing marker
This marker is a shortcut to apply the deps.maybe
marker to several elements organized in an array. Hence, valueX
elements can be anything that can be passed as value
to deps.maybe
. Please, carefully read docs about deps.maybe
before using this
#
deps.allMaybeNullrun preventing maker
This marker is a shortcut to apply the deps.maybeNull
marker to several elements organized in an array. Hence, valueX
elements can be anything that can be passed as value
to deps.maybe
. Please, carefully read docs about deps.maybe
and deps.maybeNull
before using this
#
deps.withMetameta setting marker
This marker allows to set some metadata on the run
call whenever value
changes (with respect to the previous render call). meta
must be a plain JavaScript object, while value can be either any JavaScript value, or the output of a deps.maybe
, deps.maybeNull
, deps.maybeGet
or deps.withMeta
invocation. For the first three situations, refer to the documentation of the appropriate marker.
This marker cannot be used to prevent the run
trigger, but its value
argument is still injected as a positional argument into the run
call
Nesting deps.withMeta
markers results in meta
objects being merged from inside to outside: this means that, in case of name clashes in properties, the winning value will be the outermost one.
As for any other Dep
-returning marker, you can call .withMeta(other_meta)
on the output of the marker, and you can also chain multiple calls. As usual in rocketjump, meta
objects will be merged from left to right.
To clarify the composition rules, observe the following example. Despite being pretty cumbersome, it should cover any possible case of priority
#
deps.withAlwaysMetameta setting marker
This marker allows to set metadata on a run
call unconditionally. The meta
argument must be a plain javascript object. Furthermore, this marker injects no arguments in the run
call (this means that neither undefined
nor null
nor any other value, just treat them as if they were never inserted when it comes to understanding which arguments the run
call will be made with)
As for the deps.withMeta
marker, this marker returns a Dep
instance, which means you can call .withMeta(meta)
on it. Please note that the following invocations are equivalent
#
deps.withMetaOnMountmeta setting marker
This marker allows to set metadata on a run
call just for the run
call that happens at mount. The meta
argument must be a plain javascript object. Furthermore, this marker injects no arguments in the run
call (this means that neither undefined
nor null
nor any other value, just treat them as if they were never inserted when it comes to understanding which arguments the run
call will be made with)
As for the deps.withMeta
marker, this marker returns a Dep
instance, which means you can call .withMeta(meta
on it. Please note that the following invocations are equivalent
#
Multiple meta setting markers in depsYou are free to use as many meta setting markers in your dependencies arrays, but remember that the engine will always squash them into a single object before starting the call. In case you have many meta setting markers, all of them will be evaluated and the results will be merged in array order, i.e. from left to right, as usual in rocketjump