6 ms·
To me, all of the code examples in this article look terrible. Is there any public website out there (not behind a login) which actually benefits from using th
by JonathanBeuys 3y ago
To me, all of the code examples in this article look terrible.
Is there any public website out there (not behind a login) which actually benefits from using these types of components with "magic data binding"?
Every time I see examples, I say "Hey, I could build that in a more readable and more performant way just using plain JS or using a template library like Handlebars".
And the answer I get is "Yes, but not for big enterprise SPAs." And I ask: Ok, where are those? And the answer is "Uuh, well, somewhere behind logins there are some, trust me!".
I tend to not believe it.
When I write real world code, I do not change some data value and expect all kinds of components in the interface to change magically. Instead, I write an event handler, which changes some data and then calls the parts of the UI that need an update. Like when a user changes a slider to change the x-axis range of a chart, then I set the data, call the update function of the chart and that's it. No need for components with data-to-ui-bindings.
- Etheryte 3y agoWell, this isn't really going to help the case, but yes, behind logins there really are some, trust me. Tongue in cheek aside, where I used to see this a lot as a contractor was on projects where multiple companies were hired and each was responsible for some small part of the system that had to interface with the rest of it. The core problem is that you might not even know which parts of the UI need to update when your data changes, so what you advise is not always possible.
- dvdkon 3y ago> When I write real world code, ..., I write an event handler, which changes some data and then calls the parts of the UI that need an update. That's your choice, and I'd argue you're creating messier code slower than someone who uses a UI framework that deals with reactivity in a principled manner. Having to synchronize the UI and data manually creates room for subtle desync errors which just can't happen in, say, Vue.
- sumuyuda 3y agoOn the flip side with reactive programming it is very easy to just connect up all the data to UI components and have a bunch of unnecessary redraws. It can encourage a set it and forget it mindset. In iOS programming, there are many articles about how easy it is to cause too many redraws with SwiftUI, leading to performance issues and a bad UX.
- jitl 3y agoI work on a large SPA: https://notion.so https://notion.so It’s a document editing application. A document title might occur in the browser’s titlebar, in the header of the main editor, in a “mention” (a link to the document) - in another document title, or in the text of a document, and in multiple places in the user’s sidebar - like in both their “Favorites” section and in the the contents of their team. When the user edits the document title, we need to update all those UI bits to render the new title. I have a hard time imagining some imperative code that iterates over all the possible views that may render a title to mutate them. Without binding/data subscription I can’t imagine Notion working at all.
- JonathanBeuys 3y agoCan you show the code of the "Favorites section"? Because to me it does not sound like it is as simple to update as "Just put a reference in the template". Because what is in the users favorites is probably defined in some other data structure. Which is what I witness in real life: In theory it sounds cool to just use data in the template and everything gets updated automatically. But in reality it is a mess with a million edge cases. Worse then just calling a list of update functions when the title changes.
- jitl 3y agoHere's pseudocode of the favorite section. The "SpaceView" is a database record that represents a specific user's "view" of a shared workspace. It has a few UUID[] list columns, one of which is the "bookmarked pages" list of Block UUIDs. Methods like `.getBookedmarkedPagesStore()` get a reference "RecordStore" object to a specific column inside the record. Reading the data from a RecordStore in a component implicitly subscribes the component to changes to that column. To render lists in general, we use a <List> component. It manages drag-and-drop behavior and efficient updates, react keys, etc. export function SidebarUserContent(props: { spaceViewStore: SpaceViewStore }) { const { spaceViewStore } = props const environment = useEnvironment() const sharedPages = useComputedStore( () => getUserSharedPages({ spaceViewStore, user: environment.currentUser, }), [environment.currentUser, spaceViewStore] ) return ( <> <SidebarSection canCreatePage={false} title={<FavoritesTitle />} list={spaceViewStore.getBookmarkedPagesStore()} /> <SidebarTeamsSection list={spaceViewStore.getJoinedTeamsStore()} /> <SidebarSection canCreatePage={false} title={<SharedTitle />} orderByList={spaceViwStore.getSharedPagesOrderStore()} orderByItems={sharedPages} /> <SidebarSection title={<PrivatePagesTitle />} canCreatePage={true} list={spaceViewStore.getPrivatePagesStore()} /> </> ) } function SidebarSection(props: { list?: RecordStore<Array<RecordId<BlockTable>>> orderByList?: RecordStore<Array<RecordId<BlockTable>>> orderByItems: Array<BlockStore> title: React.ReactNode canCreatePage: boolean }) { const { title, canCreatePage, ...listProps } = props return ( <div style={SidebarSectionStyle}> <SidebarTitle actions={canCreatePage ? <CreatePageButton /> : undefined} > {title} </SidebarTitle> <List {...listProps}> {blockStore => <SidebarPageItem store={blockStore} />} </List> </div> ) } Here's the pseudocode for the main editor view. The document itself is a "Page" block database record. We render editable text with the <Text> component. We listen to contentEditable document changes, and reconcile the mutations with the rendered <Text> components to understand where the user typed and how to update our database records in response to that activity. All updates to database records happen inside a call to `transactionActions.createAndCommit`. Under the hood, this: 1. Builds a list of commands to send to our server, to persist the user's edit to the database. Once the edits are applied on the server, we notify collaborators those records have changed, so the collaborators can pull new data and re-render their own views. 2. Optimistically updates the local, in-memory version of the database records to reflect the change commands. 3. Schedule rendering views and recomputing derivations of the views/derivations that read from the changed records. function PageViewBlock(props: { store: BlockStore }) { const handleFavoritePage = useCallback(() => { transactionActions.createAndCommit(transaction => { const spaceViewStore = GlobalAppStore.state.currentSpaceViewStore listActions.append( transaction, spaceViewStore.getBookmarkedPagesStore(), props.store.id, ) }) }, [props.store]) return ( <ContentEditableRoot> <Text style={PageVieBlockTitleStyle} store={props.store.getTitleStore()} /> <FavoritePageButton onClick={handleFavoritePage} /> <List list={props.store.getContentStore()}> {blockStore => <DraggableBlockListItem store={blockStore} />} </List> </ContentEditableRoot> ) } function ContentEditableRoot(props: { children: React.ReactNode }) { const [element, setElement] = React.useState<HTMLDivElement | null>(null) useMutationObserver(element, mutations => { transactionActions.createAndCommit(transaction => { const edits = getEditsToBlocks(mutations) textActions.applyEdits(transaction, edits) }) }) return <div contentEditable={true} ref={setElement}> {props.children} </div> } Our React components "just" render the state of our database rows, but because we use a generic auto-tracking state system and publish change events, our views respond in near-real-time to both local and remote data changes. A new hire engineer at Notion can create a new collaborative feature on their first day with end-to-end reactivity "just" by a) rendering part of the record in a React component, and b) updating that data in the record with transactionActions.createAndCommit. If you're still curious about our data model, I wrote a blog post about it: https://www.notion.so/blog/data-model-behind-notion https://www.notion.so/blog/data-model-behind-notion
- koromak 3y agoI would love to see you post source code like this, for any sufficiently complex frontend. Theres a reason people don't do this.
- JonathanBeuys 3y agoCan you give an example of a "sufficiently complex frontend" which is not behind a login wall, so we can actually all look at and discuss it?
- BoorishBears 3y agoSomeone already linked Notion.so and you didn't seem to find it complex enough? The reality is there's a continuum between what is fluff and what is providing value you don't see. I agree a lot of web development ceremony is misguided and of dubious benefit, but you're seemingly against even basic state propagation. But you also seem biased in thinking of systems where you can hold the whole thing in your head. When a site like Facebook shows you a chat message, there are is the an iceberg of functionality that you don't see and can't imagine with that bias. You think "I'd just use JS and add a div styled like X", and Facebook probably has more JS to enable defining what X looks like because that div can have anything from a text message, to a video, to a chess move from a FB game, than you can picture for an entire site. The analytics and tapbacks and a million little features that would make for hundreds of listeners on some seemingly simple component — At the end of the day not everyone is making Facebook, so I agree people are complicating things for dubious benefit, but not so much that basic React is problematic: even for small applications it's a cheap way to support the "combinatorial explosion of edge cases" problem of product development. You add one edge case to a state change you wired with plain JS and things are fine. But then you add an edge case to that edge case, and so on, and eventually you'll either just have a worse version of what React offers, or an unapproachable mess of code.
- JonathanBeuys 3y agoNotion is behind a login. So not easy for everybody here to look at and discuss. If there is not a single public website out there which benefits from frontend frameworks with data-biding components, that probably tells us something.