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 functions#
Whenever 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.
Props#
When 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 logic#
If 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 Paths#
Please 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:
Breakpoints#
Listeners have a powerful trick up their sleeve: breakpoints!
You use them to handle two very common scenarios:
Debouncing. Suppose we have a textfield for a
usernameand 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:
Events#
You 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:
Defaults#
There 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 together#
Kea 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
dashboardLogiccould listen to theusersLogic.actionTypes.loadUsersSuccessaction and then call its ownrefreshDashboard()action. - The
usersLogiccould listen to its ownloadUsersSuccessaction 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 logic#
There'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 connections#
While 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.