RocketJump constructor
The RocketJump Constructor is the a multi-purpose tool, as it can be used to craft two different (yet similar) kinds of objects:
- RocketJump Objects are full-fledged ready-to-use objects you can import and exploit in your components
- RocketJump Partials are not ready to use, and are primarily intended to share configuration between different RocketJump Objects
To clarify the difference, we can state this parallelism with OOP:
- RocketJump Objects play the same role as classes (can be istantiated, for instance)
- RocketJump Partials play the same role as abstract classes (cannot be instantiated, but can be composed)
- ...and React RocketJump provides multiple inheritance
- but you can only inherit from abstract classes (i.e. every non-abstract class is sealed)
Beware, this is not a strict parallelism, it is just to clarify the difference between RocketJump Objects and RocketJump Partials. In practice, when we use the RocketJump Constructor what happens behind the curtains is that configuration objects are squashed into a single one, that is then used as a recipe to instruct the engine about how to deal with a side-effectful operation. You can find more details later, in the composition section
The RocketJump constructor takes an arbitrary number of arguments
each of which is a RocketJump partial
except for the last one, which is a plain object.
The only required argument is the config object, hence it is allowed to call the rj
constructor without any partial in it.
The kind of output (RocketJump Object vs RocketJump Partial) depends only on the config
parameter: if it contains an effect
property (see later), the output will be a RocketJump Object, otherwise it will be a RocketJump Partial
In the rest of this document, we will describe the properties you can use when writing a config
object.
#
The config objectThe config object can contain the following properties
#
effect(...args) => Promise | (...args) => Observable
The effect
configuration property is very important, since when you set it the library will create a RocketJump Object instead of a Partial. This property is expected to contain the async task you want to manage: this can be any function you wish, provided that you return a Promise
or an Observable
provided by rxJS. The rest is up to you.
Parameters of the function you pass are in principle up to you: you are free to define them as you wish, but please note that some other configuration properties (like effectCaller) may influence the way parameters are passed to your function.
Example
Making a simple RocketJump Object that makes a GET request (using SuperAgent in our example, but you can use any library of choice) to some host
Example
Making a simple RocketJump Object that retrieves data about a user using rxJS Ajax tools
There are some situations in which you may want to create a bare RocketJump Object wrapping a single task without extending any RocketJump Partial and without settings other configuration options apart from effect. In this case, there is a shortcut syntax that allows you to invoke the RocketJump Constructor like this
Example
Making a simple RocketJump Object that makes a GET request (using SuperAgent in our example, but you can use any library of choice) to some host
#
namestring
A simple name for the RocketJump Object. This should be defined only when effect is defined, and its only effect is to include actions passing through any instance of the RocketJump Object in the logger. That means that, whenever you instantiate a RocketJump Object with the name property set, it will attach to the logger and stream actions to it so that these are logged properly
In composition, this property is a overwrite property (more info in composition section)
#
actionsObject<Action> => Object<Action>
React-RocketJump creates some predefined actions to manage asynchronous tasks. These actions are
run
, which starts a run of the taskcancel
, which drops any pending run of the taskclean
, which is likecancel
but also cares of resetting the stateupdateData
, which is not asynchronous and is just used to replace the data contained in the state with some other data
This function is used to transform predefined actions, or to add custom ones. It is passed an object containing the actions defined in imported RocketJump Partials and the predefined run
, cancel
, clean
and updateData
actions, and it is expected to return an object containing other actions (the keys in the object are the names of the actions). The output of the function is then merged with its input: this allows you to overwrite some action, while keeping other actions untouched, without writing plenty of boilerplate code. A common use for this is to change the way in which the predefined actions are run, or to add completely custom actions.
An action is nothing more than a function returning either a JS object or a RocketJump Action.
- If the return value is a plain JS object, it is treated as a plain, synchronous action, and dispatched without further treatment to the reducer defined for the RocketJump Object. When you define some action of this class, you will probably want to configure the reducer that manages the state to handle your custom action
- RocketJump Actions are instead created with the
makeAction
helper provided by the library, and their intrinsic feature is that they are not sent to the reducer, but to the effect pipeline, which in turn decides which action(s) send to the reducer. The effect pipeline is the only place you can work with asynchronous code, await for tasks to complete, and trigger plain actions on the reducer. You may hook into the side effect management with the effectPipeline configuration property.
Just to make some examples:
run
,clean
andcancel
are RocketJump Actions created withmakeAction
and handled by the default effect pipelineupdateData
is a plain action handled by the default reducer
In composition, this property is a chain property (more info in composition section)
Example
This partial is used to change the behaviour of the run function so as to copy the first param into the metadata associated with the action
#
selectorsObject<Selector> => Object<Selector>
When you instantiate a RocketJump Object in a React Component, one of the things you get is a state object, which contains some data. The exact structure of the state object is discussed here, but for the sake of this property we can treat it as a black box. Selectors are in fact a way to drill data out from the state object. A selector is hence a function that takes a state object and returns some property of it.
This configuration property can be used to transform predefined selectors or to add new ones. It is passed an object containing some selectors and it is expected to return, again, an object containing some other selectors. The keys of those objects are the name of the selectors. The selector bag returned by the function is merged with that given as input: this allows you to pass some selectors untouched while overwriting or adding other ones. When creating new selectors, you may want to leverage some of the default ones, like in the example below.
Default selectors are
- getData, which extracts from the state the resolved value of the last (successful) effect execution
- getError, which extracts from the state the error value of the last (rejected) effect execution
- isPending, which extracts from the state a boolean value that is true when some instance of the effect is running
In composition, this property is a chain property (more info in composition section)
Example
This partial is used to create a selector that converts a string from the state to upper case before returning it to the component
#
reducer((state, action) => state) => ((state, action) => state)
When you instantiate a RocketJump Object in a React Component, one of the things you get is a state object, which contains some data. How these data are structured and how these data transformed along time is up to the Reducer function: a reducer is a pure function (i.e. same input means same output, no side effects, no asynchronous code, nothing more). When some events happen, an action object is generated and the reducer is asked to compute next state starting from the previous state and the action.
By default, React RocketJump provides a reducer that handles some actions/events related to task management:
- user asks to trigger a new effect execution
- a new effect execution is started
- an effect execution completes
- an effect execution rejects with some error
You can customize or extend the base reducer with the reducer configuration option. This option is passed the base reducer and it is expected to return the new reducer. You are invited to use this property wisely: if you need to handle some custom plain actions, provide a reducer that handles them and call the base reducer that is passed to you as a parameter for any other action (this, for instance, ensures that library actions are handled by the default reducer)
When writing a reducer you should have a deep knowledge of the state of the component. We recall the default state shape here
Note that RocketJump Partials (or plugins, which are in the end implemented as RocketJump Partials), may change this shape
In composition, this property is a recursive property (more info in composition section)
Example
This configuration describes the extension of a reducer to add support for a new action, whose behaviour is to swap the surname and the name of a person
In some situations, you may need to hook into the handling of a library action like the ones defined above. Those actions are objects with the following shape:
#
computed{ [key: string]: string }
This configuration option allows to transform the state shape that will be exposed to the outside world (i.e. the React world) when a RocketJump object carrying it is connected (keep on reading, connection is explained in the next section). The value accepted by this property is a plain JavaScript objects whose keys are arbitrary strings and whose values are selector names (i.e. keys of the selector bag defined a couple of paragraphs above). From the engine point of view, setting this key means asking him to provide the outside world with a shadow state object. This object is derived from the original state (which is kept untouched behind the curtains) with the following logic: for each key in the computed
property, take the associated selector and invoke it on the original state object; the output of this invocation will be the value associated to the current key in the shadow state.
The computed prop is a opt-in prop: if the configuration object or any plugin defines it, the shadow state is created and there is no way to access props that the shadow state does not expose (except when using the mapStateToProps
parameter in the RocketJump Object connection, see here). If otherwise neither the config nor any plugin define it, the original state is directly exposed. Hence, when working with plugins, pay attention whether this prop is defined or not.
Beware that you cannot bind a selector multiple times: if you do it, the latest binding in composition order wins.
In composition, this property is a merged property (more info in the composition section)
Example
In this example, we create a shadow state object with only the output of getData(originalState)
exposed as a result
property. This means we won't be able to access isPending
or error
without sing the mapStateToProps
parameter in the RocketJump Object connection, see here.
Example
In the following example, only yyy
will be defined in the connected RocketJump Object
(this is done to prevent multiple evaluations of the same selector)
#
composeReducer((state, action) => state)[]
This setting can be confused with the reducer one, as it again modifies the reducer, but the way it operates is quite different. The reducer
key allows to replace the old reducer with a completely new, custom reducer, while composeReducer
produces a new reducer that chains its arguments (the first implicit argument is the base reducer we dealt with when talking about the reducer
setting). When an action is dispatched to an instantiated RocketJump Object (i.e. a RocketJump object inserted into a React Component, refer to this for more information), the reducer is in charge of computing the next state. If this configuration option is set, every reducer in the array it provides is involved in the computation: the i-th element in the array is invoked with the output of the (i-1)-th item as the old state and the dispatching action as arguments, and its return value becomes the old state for the (i+1)-th reducer. The old state for the first item in the array is the output of the reducer created with the reducer configuration property (or by the default reducer if the reducer configuration is not given), and the state that will be persisted at the end of the computation is the output of the last reducer in the array.
This setting has also a quick overload signature to avoid passing arrays of just one element, which is
(state, action) => state
. The behaviour of the following descriptions is the same
In composition, this property is (indirectly) a recursive property (more info in the composition section). We say "indirectly" because this property is squashed onto the reducer property before composition applies (i.e. composition sees a opaque reducer function, which inside implements the described behaviour).
Example
This configuration describes the extension of a reducer to add support for a new action
#
effectCaller(effect, ...params) => Promise | (effect, ...params) => Observable
This setting is used to hook into the process of launching the effect, and can be used to alter its params or to add some more of them. The first argument is the effect
to be called, and the subsequent arguments are the arguments the user sent in the run
action. You can do everything you need here, the important thing is that you call the effect and return the Promise used to await for task completion, or an Rx Observable (this is useful if you are using rxJS Ajax).
In composition, this peroperty is a recursive property (more info in the composition section)
Example
Injecting a new argument, for instance an authentication token
#
mutationsObject
This setting is used to combine multiple side-effects working on the same data, like a bunch of tasks that write to a common state. This is useful, for instance, when you deal with REST APIs: you put as effect
the GET
call, and map all the other HTTP verbs on mutations, since, at the end, they are all tasks that write the same state.
Read the complete documentation about mutations
This property can be defined only in the same object defining the effect
configuration property, so they are not involved in composition.
#
takeEffectstring | [policy: string, ...arguments] | (observable: RxObservable, mapTo: action => RxObservable) => RxObservable
This setting is used to control what to do when more instances of a task are spawned concurrently. The policy
argument is a String
, and can assume one of the following values
latest
: cancel any pending task except for the latest, or, in other words, when a task is launched it tells the engine to kill all the pending instances before launching itevery
: never cancel a task, just await all of them to complete. No grants about order of completion is set.exhaust
: if there is a pending instance of the task, it ignores any other attempt to spawn other instances of the same taskgroupBy
: compute a key for each task invocation, and behaves like thelatest
policy with respect to tasks with the same key.groupByExhaust
: compute a key for each task invocation, and behaves like theexhaust
policy with respect to tasks with the same key.
The only case in which it is required to pass an argument is the groupBy
case, where a key making function is required. The key making function is called with the run
action object as a parameter, and is expected to return a scalar (int or string does not matter).
The run action object has the following shape
You can also pass a function to this configuration property in order to define your own policy. This function is called with the following arguments:
action$
mergeObservable$
state$
extraSideEffectObs$
mapActionToObserable
prefix
This function is expected to return an Observable
provided by rxJS
Example
Group tasks by a meta key
In composition, this is a overwrite property (more info in the composition section)
#
effectPipeline(actionObservable: RxObservable, stateObservable: RxObservable) => RxObservable
This is used to customize the pipeline used to dispatch actions to observables. It is passed in a function that is called with the action stream and the state stream and it is expected to return again an RxObservable. This configuration setting is useful to introduce some transformations supported by RxJs, for instance debounce
Example
Debounce an API call
State Observable also expose a .value
field to access the current state value.
Example
Pass to effect the current next token.
Or if your prefer a more reactive approach: