kea
repository·master·Indexed 24 days ago
https://github.com/keajs/keaA state management library for handling complex application logic and state transitions. Kea provides a smart front-end architecture featuring a core plugin system for actions, reducers, selectors, and listeners, as well as React hooks like useValues, useActions, and useKea for lifecycle management. It supports keyed logic, a defaults API for initial state, and can be used in non-React environments via manual mounting.
What's inside kea
- Kea v3 is a library for managing complex state in applications. For detailed usage instructions, API references, and advanced guides, please refer to the official documentation at https://keajs.org/.
Configure Kea context and plugins with resetContext()
masterIn Kea 1.0+, all plugins and configuration options must be defined on the context rather than passed to individual
kea()calls. The recommended way to initialize your application is to callresetContext()at a high level in your app. This is particularly useful for server-side rendering as it allows you to clear the cache for each render.To set up your application:
- Call
resetContext()with your desired plugins and options. - Retrieve the
storefrom the context usinggetContext(). - Pass that store to your React-Redux
<Provider>.
// 1. Initialize context with plugins and options resetContext({ plugins: [sagaPlugin, localStoragePlugin], // other options like defaults, plugin config, redux strategy, etc. }); // 2. In your App component, get the store and provide it function App ({ children }) { const { store } = getContext() return ( <Provider store={store}> {children} </Provider> ) }- Call
Upgrade to Kea 1.0+
masterTo upgrade to Kea 1.0+, ensure your environment meets the following dependency requirements:
react-redux: version7.1or laterreact: version16.8.3or later
Then, upgrade all related Kea packages to their latest 1.0+ versions:
keakea-sagakea-thunkkea-localstorage
Understand the Logic interface
masterThe
Logicinterface is the fundamental building block of Kea. It represents a self-contained unit of state, actions, and selectors. ALogicobject contains:- Identity:
path,pathString, andkey(for keyed logic). - Actions:
actionCreators,actionKeys,actionTypes,actions, andasyncActions. - State Management:
defaults,reducers, andselector(to derive state). - Selectors:
selectors(a record of functions to extract specific pieces of state). - Lifecycle Events:
events(e.g.,beforeMount,afterMount,propsChanged). - Listeners:
listenersandsharedListenersfor reacting to actions.
- Identity:
How selectors and prop selectors work together
masterIn Kea, selectors are designed to be reactive to both the global state and the local logic props. When defining a selector via
selectors(), the input function provides two specialized objects:selectors: Access to other selectors defined within the same logic. This allows for composing selectors.propSelectors: A proxy-like object used to access the logic's props. Instead of accessingprops.iddirectly, you use thepropSelectorsto return a function that retrieves the prop. This ensures the selector remains memoized and correctly tracks prop changes.
Example Pattern:
selectors({ // A base selector accessing state duckId: [(s) => [s.duckId], (id) => id], // A composed selector using another selector and a prop duckAndChicken: [ (s, p) => [s.duckId, p.id], // s.duckId is the selector above, p.id is a prop (duckId, id) => duckId + id ] })Use BuiltLogic and LogicWrapper for mounting and extending
masterWhen you define a logic, Kea returns a
BuiltLogicor aLogicWrapper. These objects provide methods to manage the logic's lifecycle and extend its functionality:mount(): Returns an unmount function. Used to start the logic.unmount(): Stops the logic.isMounted(keyOrProps?): Checks if a specific instance of the logic is currently mounted.find(keyOrProps?): Returns a mounted logic instance or throws if not found.findMounted(keyOrProps?): Returns a mounted logic instance ornull.extend(extendedInput): Creates a newLogicWrapperby extending the current logic with new inputs (actions, reducers, etc.).wrap(Component): Wraps a React component to connect it to the logic, returning aKeaComponent.
How reducers() and selectors work together
masterIn Kea, state management is tightly coupled. When you define a key in
reducers(), Kea performs several side effects to ensure the state is accessible:- Path Selection: It uses
rootSelector()to create a base selector that points to the logic's specific slice of the Redux state usingpathSelector. - Selector Creation: For every key in your
reducersdefinition, Kea usesaddSelectorAndValueto create a memoized selector (viareselect) that extracts that specific key from the logic's state slice. - Default Values: If no initial value is provided in the array, Kea attempts to resolve a default value from
getContextDefaultsor a global root default selector (logic.defaults['*']).
This ensures that as soon as you define a reducer, you immediately have a corresponding selector available to read that state.
- Path Selection: It uses
How pauseListenersEnhancer works
masterThepauseListenersEnhanceris a built-in Redux store enhancer used by Kea to prevent unnecessary re-renders. It intercepts Reduxsubscribecalls and checks if the application is currently in a 'paused' state (usingisPaused()). If the application is paused (typically during logic mounting), the observer is prevented from executing, which avoids early React re-renders caused by state changes during the initialization phase.Implement Listeners with Breakpoints
masterA
ListenerFunctionallows you to react to actions. It receives the action's payload, the action itself, the previous state, and aBreakPointFunction.BreakPointFunctionis a special function that can be used to pause or delay execution. It can be called without arguments to return immediately, or with a millisecond value(ms: number) => Promise<void>to create a delay.// Example signature of a listener type ListenerFunction<A extends AnyAction = any> = ( payload: A['payload'], breakpoint: BreakPointFunction, action: A, previousState: any, ) => void | Promise<void>;Initialize and manage the Kea context
masterThe Kea context is the central registry for plugins, stores, and logic. You manage its lifecycle using
openContext,closeContext, andresetContext.openContext(options, initial): Creates and activates a new context. If a context is already open, it will log an error to the console. By default, it automatically creates a Redux store unlesscreateStore: falseis passed in the options.closeContext(): Tears down the current context and triggersbeforeCloseContextplugin events.resetContext(options, initial): A convenience method that closes the existing context and opens a new one with the provided options.getContext(): Retrieves the currently active context.
Note on Store Creation: The Redux store is lazily initialized. It is created the first time
context.storeis accessed, unless you provide a custom store viaoptions.createStoreor setcreateStore: false.Use Kea hooks: useValues, useActions, and useAllValues
masterKea provides several React hooks to interact with logic.
useValues(logic): Returns the state of the logic's values. Note: This hook returns getters that calluseSelectorunder the hood. You must directly destructure the returned object (e.g.,const { name } = useValues(logic)) and not store the whole object in a variable to use later.useAllValues(logic): Use this if you need to store the entire values object in a variable for later use. This is the safe alternative to storing the result ofuseValues.useActions(logic): Returns the actions defined in the logic.
Logic is automatically mounted and unmounted when using these hooks in a component.
function NameComponent () { const { name } = useValues(logic) // Destructure directly const { updateName } = useActions(logic) return ( <div> <div>Name: {name}</div> <button onClick={() => updateName('George')}>Change</button> </div> ) }Use Kea without React
masterIf you have a configured Kea context and a connected Redux store, you can use Kea in non-React environments by manually mounting logic.
For non-keyed logic, use
logic.mount(). For keyed logic, you must first build it with props usinglogic(props), then call.mount()on the resulting object.mount()returns an unmount function to clean up the logic and disconnect it from the store.