Skip to main content

Introduction to Kea 3.0 in 30 minutes

In this tutorial we'll cover actions, reducers, listeners, selectors, afterMount, kea-forms and kea-router.

This tutorial is a high level introduction to Kea, and doesn't go deep on all the touched subjects. The next tutorial will start from the basics, and build strong core foundations in Kea.

Watch now: Tutorial 1

Code samples

Here is the code that will build in the tutorial:

Simple.tsx

import {
actions,
kea,
reducers,
useActions,
path,
useValues,
listeners,
afterMount,
beforeUnmount,
} from 'kea'

import type { simpleLogicType } from './SimpleType'

const simpleLogic = kea<simpleLogicType>([
path(['App', 'Simple', 'Simple']),
actions({
increment: true,
decrement: true,
}),
reducers({
counter: [
0,
{
increment: (state) => state + 1,
decrement: (state) => state + 1,
},
],
}),
listeners({
increment: async (_, breakpoint) => {
console.log('up and to the right!')
await breakpoint(1000)
console.log('only once')
},
}),
afterMount(({ actions, cache }) => {
cache.interval = window.setInterval(() => {
actions.increment()
}, 1000)
}),
beforeUnmount(({ cache }) => {
cache.interval && window.clearInterval(cache.interval)
}),
])

export function Simple() {
const { increment, decrement } = useActions(simpleLogic)
const { counter } = useValues(simpleLogic)

return (
<div>
<div>Counter: {counter}</div>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
)
}

LoginForm.tsx

import './LoginForm.scss'
import { kea, path, useValues } from 'kea'
import { forms, Form, Field } from 'kea-forms'
import type { loginLogicType } from './LoginFormType'

const loginLogic = kea<loginLogicType>([
path(['App', 'LoginForm', 'LoginForm']),
forms(({ actions }) => ({
loginForm: {
defaults: { user: '', pass: '' },
errors: ({ user, pass }) => ({
user: !user ? 'Please enter a user' : '',
pass: !pass ? 'Please enter a password' : '',
}),
submit: async ({ user, pass }, breakpoint) => {
console.log(user, pass)
await breakpoint(1000)
console.log('we are in')
actions.resetLoginForm()
},
},
})),
])

export function LoginForm(): JSX.Element {
const { isLoginFormSubmitting } = useValues(loginLogic)
return (
<Form logic={loginLogic} formKey="loginForm" enableFormOnSubmit className="LoginForm">
{/* `value` and `onChange` are passed automatically to children of <Field> */}
<Field name="user" label="Username">
<input type="text" />
</Field>
<Field name="pass" label="Password">
<input type="password" />
</Field>
<button type="submit" disabled={isLoginFormSubmitting}>
Login!
</button>
</Form>
)
}

App.tsx

import React from 'react'
import './App.scss'
import { Simple } from './Simple/Simple'
import { LoginForm } from './LoginForm/LoginForm'
import { actions, kea, reducers, path, useValues, selectors, useActions } from 'kea'

import type { sceneLogicType } from './AppType'
import { actionToUrl, router, urlToAction } from 'kea-router'

export enum Scene {
LoginForm = 'login',
Simple = 'simple',
}

const scenes: Record<Scene, () => JSX.Element> = {
[Scene.LoginForm]: LoginForm,
[Scene.Simple]: Simple,
}

export const sceneLogic = kea<sceneLogicType<Scene>>([
path(['App', 'sceneLogic']),
actions({ setScene: (scene: Scene) => ({ scene }) }),
reducers({ scene: [Scene.LoginForm as Scene, { setScene: (_, { scene }) => scene }] }),
selectors({ Component: [(s) => [s.scene], (scene) => scenes[scene]] }),

actionToUrl({ setScene: ({ scene }) => `/${scene}` }),
urlToAction(({ actions, values }) => ({
'/': () => {
router.actions.push('/login')
},
'/:scene': ({ scene }) => {
if (scene && values.scene !== scene) {
actions.setScene(scene as Scene)
}
},
})),
])

function Menu() {
const { scene } = useValues(sceneLogic)
const { setScene } = useActions(sceneLogic)
return (
<div style={{ margin: 20 }}>
{Object.keys(scenes).map((key) => (
<button
key={key}
onClick={() => setScene(key as Scene)}
style={{ fontWeight: scene === key ? 'bold' : 'normal' }}
>
{key}
</button>
))}
</div>
)
}

export function App() {
const { Component } = useValues(sceneLogic)
return (
<div className="App">
<Header />
<Menu />
<div className="App-layout">
<Component />
</div>
</div>
)
}

Questions & Answers

Ask questions about this page here.