Additional Concepts
note
This page builds upon the Core Concepts. Read that first if you haven't yet.
The concepts discussed below are all worth knowing, yet some might be more immediately useful than others. Have a look and then come back when you need them.
#
Input objects vs functionsWhenever you're using any of kea's built-in primitives (actions
, reducers
, listeners
, etc),
you have two options.
You can pass objects to them:
... or you can pass functions to them:
What's the difference?
First, if you pass a function, it gets evaluated lazily when the logic is built.
If you're using values that are not guaranteed to be there (e.g. a reducer that uses
otherLogic.actionTypes.something
), pass a function:
Second, the function you pass gets one argument, logic
, which you
can destructure to get actions
, values
and other goodies on the logic that you're building:
The recommendation is to write the simplest code you can (start with an reducers: {}
)
and when you need to access actions
, values
or perform lazy evaluation, convert it into
a function.
#
PropsWhen you use logic()
as a function and pass it an object as an argument, that object will be saved
as props
:
Calling logic(props)
is a fast operation. Only the props
on the logic will be updated in case the
logic is already mounted. Thus it's safe to call from a React component.
You can pass random data from React onto the logic this way. For example various defaults.
It's as simple as this:
Then just use props
wherever you need to. For example:
#
Keyed logicIf you give your logic a key
, you can have multiple independent copies of it. The key is derived
from props
:
Now every time you call userLogic({ id: 1 })
with a new id
, a completely independent
logic will be built and mounted.
This is really handy when you have data that's passed as a props in React, such as:
No matter how many times <User id={1} />
is rendered by React, it'll always be connected
to the same logic.
If you render <User id={2} />
, it'll however get its own independent copy of this same base logic
and do what is needed to load and display the second user.
#
Keys and PathsPlease note, if you manually specify a path for your keyed logic,
you must specify it as a function that takes one argument, key
, like so:
#
BreakpointsListeners have a powerful trick up their sleeve: breakpoint
s!
You use them to handle two very common scenarios:
Debouncing. Suppose we have a textfield for a
username
and you want to fetch the github repositories for whatever is typed in there. If the user types"keajs"
, you will actually make five requests ("k"
,"ke"
, ...), while only the last one ("keajs"
) matters. It's smarter to wait a few hundred milliseconds before making a request in case the user enters another character.Out-of-order network requests. In the example above, suppose we intend to search for
"keajs"
. We type"ke"
and pause for a moment. A network request gets sent to fetch the repositories for the user"ke"
. We then complete the string into"keajs"
and make another request. What happens if the first request for"ke"
is slow and comes back after the request for"keajs"
has already finished? Without tracking this explicitly, we might incorrectly override the list of repositories and show whatever network request finished last, no matter what username is in the searchfield.
Breakpoints solve both of those scenarios. They are passed as the second argument to listeners,
after the payload
.
If you call await breakpoint(delay)
, the code will pause for delay
milliseconds before
resuming. In case the action you're listening to gets dispatched again during this delay,
the listener for the old action will terminate. The new one will keep running.
In case the logic unmounts during this delay, the listener will just terminate.
If you call breakpoint()
without any arguments (and without await
), there will be no pause.
It'll just check if the listener was called again or the logic was unmounted and terminate if that's
the case. You can use this version of breakpoint()
after long running calls and network requests
to avoid those "out of order" errors.
Here's an example that uses both types of breakpoints:
Under the hood breakpoints just throw
exceptions.
In case you must call a breakpoint from within a try / catch
block, use the isBreakpoint
function to check if the caught exception was from a breakpoint or not:
#
EventsYou can hook into the mount and unmount lifecycle of a logic with events
:
The useful events are afterMount
and beforeUnmount
, as when they are called
you have access to all the actions
, values
, etc of the logic.
All events accept either a function or an array of functions:
#
DefaultsThere are two ways to pass defaults to reducers. We've been using this style until now:
If you choose, you can set your defaults explicitly in a defaults
object:
In case you pass both, the value in the defaults
object will take precedence.
You can also pass selectors as defaults:
You can take it one level deeper and return a selector that computes the defaults:
#
Connecting logic togetherKea is said to be a really scalable state management library. This power comes partially from its
ability to link together actions and values from different logic
.
note
Automatic connections are implemented in Kea versions 2.0 and later. Explicit connections (described below) work in all versions of Kea.
Wiring logic together is easier than you think. Suppose we have these two logics:
Our pointy-haired-boss now tasked us with reloading the dashboard every time the users are successfully loaded. How do we do that?
Just use the loadUsersSuccess
action from usersLogic
as a key in the listeners
object:
note
In the example above we had two options:
- The
dashboardLogic
could listen to theusersLogic.actionTypes.loadUsersSuccess
action and then call its ownrefreshDashboard()
action. - The
usersLogic
could listen to its ownloadUsersSuccess
action and then calldashboard.actions.refreshDashboard()
.
We went for the first option. Why?
It really depends on the use case. Here I'm assuming that usersLogic
is some global logic that
stores info on all available users and is accessed by many lower level logics throughout your app,
including dashboardLogic
.
In this scenario, usersLogic
can exist independently, yet dashboardLogic
can only exist together
with usersLogic
. Linking them the other way (having usersLogic
call dashboardLogic
's action)
would mean that the usersLogic
is always mounted together with dashboardLogic
, even for instance
when we are not on the dashboard scene. That's probably not what we want.
This [otherLogic.actionTypes.doSomething]
syntax also works in reducers:
and selectors:
... and everywhere else you might expect.
What if when refreshing the dashboard we need to do something with the list of users?
Just get the value directly from usersLogic.values
:
In all of these cases, usersLogic
will be automatically connected to the logic that called it
and mounted/unmounted as needed.
#
Mounting another logic together with your logicThere's one caveat here. If the first time you access a value on usersLogic
is inside a listener,
the logic will be mounted only then. If usersLogic
has a afterMount
event that loads and fetches data,
it'll probably not have done its work by then.
If you want to hook two logics together, so that they are mounted at the same time, use connect
:
This is not needed if you use otherLogic
as a key in reducers or listeners. It's only needed if you
access elements on otherLogic
inside a listener.
#
Explicit connectionsWhile the automatic connections might seem self-evident, there's actually a lot that's happening under the hood.
Kea's logic is always lazy, meaning it's not built nor mounted before it's needed. In the examples
above, if the first time usersLogic
is referenced is when its actions
are used as keys in
dashboardLogic
's reducers
, it will get built and mounted then and there.
This wasn't always the case. Before version 2.0 there was no guarantee that usersLogic
was already
mounted and that usersLogic.actions
would be available for use. You had to track this manually.
You could either explicitly mount usersLogic
in your component or you could use connect
to
pull in actions and values from other logic. You can still do this in Kea 2.0 and it has its uses.
The syntax is as follows:
Next steps
- Read Using with React to learn about all the ways you can use Kea with React.
- Read the Advanced Topics page for even more things Kea can do.