Skip to main content

3 posts tagged with "data-first"

View All Tags

Live-Replay Logic Testing

Marius Andra

Marius Andra

Kea Core Team, Software Engineer at PostHog

Big news, we have a brand new logic testing framework!

Read all about it in the updated Testing guide!

Here's a teaser:

import { expectLogic, partial } from 'kea-test-utils'
it('setting search query loads remote items', async () => {
await expectLogic(logic, () => {
logic.actions.setSearchQuery('event')
})
.toDispatchActions(['setSearchQuery', 'loadRemoteItems'])
.toMatchValues({
searchQuery: 'event',
remoteItems: partial({
count: 0,
results: [],
}),
remoteItemsLoading: true,
})
.toDispatchActions(['loadRemoteItemsSuccess'])
.toMatchValues({
searchQuery: 'event',
remoteItems: partial({
count: 3, // got new results
results: partial([partial({ name: 'event1' })]),
}),
remoteItemsLoading: false,
})
})

Oh, and Kea 2.5 is out as well, featuring logic.isMounted() and a bunch of fixes from the 2.4 series.

Kea-TypeGen 1.0: Auto Import

Marius Andra

Marius Andra

Kea Core Team, Software Engineer at PostHog

Finally, files generated with kea-typegen will automatically import any types they can, and add the rest as type arguments for kea<logicType<LocalType, LocalUser>>

You just need to add types to your actions and reducers.

import { Blog } from './blog'
import { logicType } from './logicType'
export const LocalType = 'YES' | 'NO'
const logic = kea<logicType<LocalType>>({ // ๐Ÿ‘ˆ๐Ÿฆœ managed automatically by typegen
actions: {
openBlog: (id: number, blog?: Blog) => ({ id, blog }), // ๐Ÿ‘ˆ add types here
closeBlog: (answer: LocalType) => ({ answer }),
},
reducers: {
blogId: [
null as number | null, // ๐Ÿ‘ˆ null now, but sometimes a number ๐Ÿ™€
{
openBlog: (_, { id }) => id,
closeBlog: () => null,
// use `actionTypes` instead of `actions`
[funLogic.actionTypes.randomBlogPage]: () => 4, // chosen by a fair dice roll
},
],
},
listeners: () => ({
closeBlog: ({ answer }) => { // no types needed here
console.log(answer)
}
})
})

Read the updated TypeScript guide to learn more.

It's time for a data-first frontend revolution

Marius Andra

Marius Andra

Kea Core Team, Software Engineer at PostHog

Back in 2015, shortly after learning about React and Redux, I fell in love with the functional programming paradigms behind them because of what they enabled.

By following a few principles of immutability and purity, React frontends were generally better written, stabler and easier to debug, compared to contemporary alternatives such as Ember or Angular.

Having seen what a bit of functional programming did to JavaScript, I started looking into Clojure, the most popular functional language at the time, and into ClojureScript frontend frameworks: reagent, quiescent, om, om.next, and re-frame. Now there's also fulcro that wasn't around back then.

What stood out was how they all handled application state.

They all had developed at least three globally isolated layers:

// obvious pseudocode
async function renderApp() {
while(await waitForChanges()) {
// gather the input (check the url and call APIs)
const input = await getInputFromURLAndAPIs(window)
// normalize that to an intermediary app state
const appState = convertInputToApplicationState(input)
// create HTML out of that intermediary state
const html = convertApplicationStateToHTML(appState)
render(html)
}
}

No framework skipped the application state step. Nobody did this:

async function renderApp() {
while(await waitForChanges()) {
// gather the input (check the url and call APIs)
const input = await getInputFromURLAndAPIs(window)
// create HTML out of that input
const html = convertInputToHTML(input)
render(html)
}
}

Yet that's exactly what you're doing when you store application state in React components.

The three layers#

All these frameworks reached the conclusion that the best way to convert API responses to DOM nodes was to first convert them to an intermediary representation: the global application state.

These three layers (input, data and view) have overlapping, yet separate and parallel hierarchies:

Kea TypeScript ResetContext

The structure of your input data is different from the structure of your application state, which is different from the structure of your DOM nodes.

The folks at Facebook/React agree: to build great user experiences, you benefit from having parallel data and view trees. They do use Relay after all for their state.

Yet the work with Concurrent Mode, Suspense, useTransition, startTransition, useDeferredValue, the suggestion to start fetching early in event callbacks, etc, is moving React in a direction where it's taking on more and more responsibilities from the data layer.

I think this is a clear step backwards for maintainability.

Wild speculation

This is pure speculation, but I think this is why Suspense is still considered experimental in 2021, despite having been in development for over 3 years. It's just not that easy to blend the data and view layers in a way that makes everyone happy. Hence things keep being pushed til "when it's done".

Put your data first#

I think it's time for a change. It's time for a paradigm shift.

It's time for a revolution in data-first frontend frameworks, which relegate React to what it does best: rendering and diffing DOM nodes. Values in, actions out. No useState. No useEffect.

In practice this means adopting a data layer as the core of your application.

It means going from:

Frontend > React > [state management library of choice]

to

Frontend > Data Layer > View Layer (React)

Rob Pike puts it best:

Data dominates. If you've chosen the right data structures and organized things well, the algorithms will almost always be self-evident. Data structures, not algorithms, are central to programming.

In all my years of programming I have yet to see an exception to this rule. On the contrary, teaching this to junior developers has proven to be the single most impactful thing in improving their code quality.

Forget JSX templates. Forget algorithms. Get your data structures right, and the rest will follow:

From that moment forward, I would see this everywhere. If ever I felt like my program was getting too complicated or hard to read, it was almost always a data structure problem. Since then, every time I've been humbled by another programmer's code, it hasn't been by clever tricks or algorithms. It has always been by their ingenious insight into how their program's data ought to be organized.

It's time for a change. It's time for a paradigm shift. It's time for a revolution in data-first frontend frameworks that control the view layer and not the other way around.

Since you're reading this on the Kea blog, I'm obviously biased as to what's the best data layer for frontend developers. Suggesting alternatives is made even more complicated by the fact that most other tools position themselves as "state management" libraries, at the mercy of React.

Kea is one of the few frameworks for managing the complete lifecycle of your data. It uses React as the view layer and integrates nicely with the existing Redux ecosystem.

Go check it out and then start writing webapps the way they were meant to be written: data first.