Skip to main content

loaders

When making network requests that fetch data, you end up writing the same thing over and over again:

  1. an action to make the request
  2. an action to handle success
  3. an action to handle errors
  4. a reducer to store the data
  5. a reducer to store the loading state
  6. a listener to make the request and return the data

The kea-loaders plugin abstracts this pattern into a system of loaders.

Installation

First install the kea-loaders package:

# if you're using yarn
yarn add kea-loaders

# if you're using npm
npm install --save kea-loaders

Optional: global callbacks

If you want to configure a global error handler for loaders (e.g. notification alert), pass the following plugin to your resetContext call:

import { loadersPlugin } from 'kea-loaders'

resetContext({
plugins: [
loadersPlugin({
// Called when any loader is started
onStart({ logic, reducerKey, actionKey }) {
// start a global spinner
},

// Called when any loader was successful
onSuccess({ response, logic, reducerKey, actionKey }) {
// stop that global spinner
},

// Called when the listener throws an error
// Feel free to alert the user in a nicer way,
// for example by displaying a notification.
// Also connect this to your bug tracking software.
onFailure({ error, logic, reducerKey, actionKey }) {
console.error(`Error in ${actionKey} for ${reducerKey}:`, error)
},
}),
],
})

Sample usage

import { kea } from 'kea'
import { loaders } from 'kea-loaders'

export const projectLogic = kea([
key((props) => props.id),

loaders(({ values, props }) => ({
project: {
loadProject: (id = props.id) => projectsService.get(id),
},

// the above code creates these actions:
// - loadProject: params => params
// - loadProjectSuccess: project => ({ project })
// - loadProjectFailure: error => ({ error })

// ... and these reducers:
// - project (whatever the loadProject loader returns)
// - projectLoading (true or false)

apiKeys: [
[],
{
// default empty array instead of null
loadApiKeys: () => apiKeysService.find({ query: { projectId: props.id } }),
createApiKey: async () => {
const apiKey = await apiKeysService.create({ projectId: props.id })
return [...(values.apiKeys || []), apiKey]
},
},
],

// the above code creates these actions:
// - loadApiKeys: true
// - loadApiKeysSuccess: apiKeys => ({ apiKeys })
// - loadApiKeysFailure: error => ({ error })
// - createApiKey: true
// - createApiKeySuccess: apiKeys => ({ apiKeys })
// - createApiKeyFailure: error => ({ error })

// ... and these reducers:
// - apiKeys (whatever the loadProject loader returns)
// - apiKeysLoading (true or false)
})),

// start the loaders after mounting the logic
afterMount(({ actions }) => {
actions.loadProject()
actions.loadApiKeys()
}),
])

export function Project({ id }) {
const { project, projectLoading } = useValues(projectLogic({ id }))
const { loadProject } = useActions(projectLogic({ id }))

return (
<div>
{projectLoading ? (
<div>Loading project</div>
) : project ? (
<div>Project: {project.id}</div>
) : (
<div>No project found!</div>
)}

<button onClick={() => loadProject(id)}>Reload project</button>

<button onClick={() => loadApiKeys()}>Load API keys</button>
{/* In case your loader function takes no arguments, we recommend */}
{/* passing onClick={() => loadProject()} in the click handler. */}
{/* Using just onClick={loadProject} will pass the click event as */}
{/* a param to the action and that may cause unexpected issues... */}
</div>
)
}

Overriding reducers

In case you need to override project or projectLoading, just override the reducers:

export const projectLogic = kea([
key((props) => props.id),

actions({
setAsFinished: true,
}),

loaders(({ values, props }) => ({
project: {
loadProject: (id = props.id) => projectsService.get(id),
},
})),

reducers({
project: {
setAsFinished: (state) => (state ? { ...state, finished: true } : state),
},
// only switch to "true" the first time the project loads by
// using `null` as a third state
projectLoading: {
loadProject: (state) => (state === false ? true : state),
loadProjectSuccess: () => null,
// loadProjectFailure is unmodified
},
}),
])

In case you want to override the default after the fact, it's not possible to do so by passing a new default to reducers. You must instead pass the new default to defaults

For example:

kea([
defaults({
projectLoading: 'not yet',
}),
])

Questions & Answers

Ask questions about this page here.