- https://redux.js.org/tutorials/essentials/part-2-app-structu...
- https://redux.js.org/introduction/why-rtk-is-redux-today
- https://redux.js.org/tutorials/typescript-quick-start
I'm also interested in hearing any particular features in Kea you find useful that aren't covered in RTK.
In my experience you need some amount of boilerplate, or what I'd call _architecture_, to have a stable system. Give too much freedom ("just use get/set" or "variables on context"), and not only can no other developer on your team not maintain your code, but no part of your app will work together after a while. You'll end up with multiple incompatible implementations of ad-hoc state machines.
The key to maintainability is to use a framework that forces you to do spend a bit more effort than you'd do when coding YOLO, but which forces everyone on the team to make the same tradeoff. The result: everyone can jump in to any part of the project and with a bit of digging around be fluent in the surroundings.
Kea seems to strike a pretty good balance of making this possible in large projects.
Here's one of the examples from the docs (https://keajs.org/docs/core/listeners/#error-handling)
kea([
actions({
loadUsers: true,
loadUsersSuccess: (users) => ({ users }),
loadUsersFailure: (error) => ({ error }),
}),
reducers({
users: [
[],
{
loadUsersSuccess: (_, { users }) => users,
},
],
usersLoading: [
false,
{
loadUsers: () => true,
loadUsersSuccess: () => false,
loadUsersFailure: () => false,
},
],
usersError: [
null,
{
loadUsers: () => null,
loadUsersFailure: (_, { error }) => error,
},
],
}),
listeners(({ actions }) => ({
loadUsers: async () => {
try {
const users = await api.get('users')
actions.loadUsersSuccess(users)
} catch (error) {
actions.loadUsersFailure(error.message)
}
},
})),
])
I argue that an API with a different shape could implement the same behavior in a more direct readable style, without giving up any of the nice debugging, etc that you get with Kea's abstractions. class Users extends OtherKeaAPI {
users = this.state<User[]>([])
usersLoading = this.state(false)
usersError = this.state<Error | null>(null)
loadUsers = this.action(action => {
this.usersLoading = true
this.usersError = null
action.effect('load em up', async () => {
try {
this.loadUsersSuccess(await api.get('users'))
} catch (error) {
this.loadUsersFailure(error)
}
})
})
loadUsersSuccess = this.action((users: User[]) => {
this.usersLoading = false
this.users = users
})
loadUsersFailure = this.action((error: Error) => {
this.usersLoading = false
this.usersError = error
})
}First, it's 2 rewrites. Second, those were all 98% backwards compatible for the users (might need to upgrade plugins), and just refactored the internals.
But sure, let's all write perfect code on day 0, taking into account all the ways libraries you depend on (React) can develop in the future. It's not like "frontend" is a moving target or anything...
/s